A Go slice can be short while still carrying much more capacity than its current length. That extra capacity is useful when more elements are expected, but sometimes code should hand off a slice without leaving room for an append to reuse the same tail. slices.Clip gives that intent a direct operation.
Clip returns a slice with the same elements and length, but its capacity is reduced to its length.
Limit slice capacity with slices.Clip
The basic call returns a new slice header, so keep the result:
package main
import (
"fmt"
"slices"
)
func main() {
buffer := make([]int, 3, 8)
buffer[0], buffer[1], buffer[2] = 10, 20, 30
buffer = slices.Clip(buffer)
fmt.Println(buffer)
fmt.Println(len(buffer), cap(buffer))
}Output:
[10 20 30]
3 3The values don’t move just because the capacity changes. Conceptually, Clip returns s[:len(s):len(s)], using a full slice expression to set the maximum capacity.
Clip doesn’t copy the elements
Reducing capacity is not the same as allocating independent storage. The clipped slice still refers to the same backing array for its existing elements.
base := []int{10, 20, 30, 40}
view := slices.Clip(base[:2])
view[0] = 99
fmt.Println(base)Output:
[99 20 30 40]Clipping base[:2] prevents that view from extending into indexes 2 and 3 through its capacity, but writes to indexes already inside the view still affect shared storage.
If independent elements are required, use slices.Clone instead. Clip changes the slice’s capacity boundary; Clone creates separate outer storage.
Append after Clip may allocate new storage
The most practical effect appears on a later append. Once capacity equals length, appending another element can’t fit inside the clipped slice’s current capacity.
base := []int{10, 20, 30, 40}
view := slices.Clip(base[:2])
view = append(view, 99)
fmt.Println(view)
fmt.Println(base)The appended slice contains 10, 20, 99, while base keeps 30 at index 2. The append needs room beyond the clipped capacity, so it obtains storage suitable for the larger result.
Without clipping, base[:2] has spare capacity and an append can reuse the backing array, potentially replacing base[2].
This makes Clip useful at ownership boundaries where existing elements may remain shared but later growth shouldn’t overwrite the hidden tail of the current backing array.
Clip doesn’t release the backing array by itself
The name can suggest a stronger memory operation than it actually performs. slices.Clip doesn’t copy the visible elements into a smaller allocation. It only returns a slice whose capacity equals its length.
That means a small clipped subslice can still keep its original backing array reachable. If the goal is to detach a small result from a much larger temporary buffer, clone the visible range:
result := slices.Clone(buffer[start:end])Cloning and clipping solve different problems. Use clipping to restrict capacity. Use cloning when separate storage is the requirement.
Nil and empty slices keep their shape
A nil slice stays nil after clipping:
var values []int
values = slices.Clip(values)
fmt.Println(values == nil)Output:
trueA non-nil empty slice remains non-nil. In both cases, capacity is reduced to length, which is zero.
This matters when code distinguishes nil from an allocated empty slice for serialization or API behavior. Clip preserves nilness rather than normalizing empty values.
Keep the returned slice
Because Clip returns a changed slice header, calling it without retaining the result has no useful effect on the caller’s variable:
slices.Clip(items)Use:
items = slices.Clip(items)The elements themselves aren’t rearranged, so ignoring the result can look harmless in testing. The missed capacity change usually becomes visible only when later code appends.
Clip at a clear ownership boundary
slices.Clip fits code that wants to keep the current elements while removing spare capacity from the slice view. Its main effect is on future growth: an append can’t reuse capacity that the clipped header no longer exposes.
Don’t use it as a substitute for copying. Existing elements can still share backing storage, and clipping alone doesn’t shrink the underlying allocation. When isolation is required, clone; when the capacity boundary is the concern, clip and keep the returned slice.