A slice assignment copies a slice header, not its elements. After b := a, both slice values can still refer to the same backing array, so an element update through one value can appear through the other. slices.Clone provides a compact standard-library operation for the cases that need a copy of the slice elements instead.
The function is part of the slices package. Its result has the same length as the input and contains the same elements, but later replacement of an element in one slice does not replace the corresponding element in the other.
Clone copies the slice elements
The generic signature accepts ordinary slices and named slice types:
func Clone[S ~[]E, E any](s S) SA direct example shows the storage separation:
package main
import (
"fmt"
"slices"
)
func main() {
source := []string{"api", "worker", "cron"}
copied := slices.Clone(source)
copied[0] = "gateway"
fmt.Println(source[0]) // api
fmt.Println(copied[0]) // gateway
}The assignment to copied[0] changes the cloned slice element. It does not replace source[0].
That differs from copying the slice value itself:
source := []int{10, 20, 30}
alias := source
alias[0] = 99
fmt.Println(source[0]) // 99Here, alias and source still address the same element storage.
The copy is shallow
Clone copies elements according to normal Go assignment semantics. It does not recursively duplicate data referenced by those elements.
For a slice of pointers, the pointer values are copied into separate slice storage, while both slices still contain pointers to the same objects:
type Config struct {
Port int
}
first := &Config{Port: 8080}
source := []*Config{first}
copied := slices.Clone(source)
copied[0].Port = 9090
fmt.Println(source[0].Port) // 9090The slice element slots are separate, but the Config object is shared. Replacing copied[0] with a different pointer would not replace source[0]; mutating the object reached through the shared pointer remains visible from both slices.
The same principle applies to elements that contain maps, slices, pointers, channels, or other reference-bearing fields. Clone duplicates the outer slice elements, not the complete object graph reachable from them.
Nil state is preserved
A nil slice and a non-nil empty slice can carry different meaning at API or serialization boundaries. slices.Clone preserves that distinction.
var nilValues []int
emptyValues := []int{}
nilCopy := slices.Clone(nilValues)
emptyCopy := slices.Clone(emptyValues)
fmt.Println(nilCopy == nil) // true
fmt.Println(emptyCopy == nil) // falseBoth results have length zero, but their nil state follows the input. Code that depends on that distinction does not need a separate special case around Clone.
Named slice types remain named
Because the type parameter uses S ~[]E and the return type is S, a named slice type is retained rather than reduced to its unnamed slice form.
type Ports []int
original := Ports{8080, 8081}
copied := slices.Clone(original)
fmt.Printf("%T\n", copied) // main.PortsThis is useful when methods or API signatures are attached to a named slice type. The clone can continue through code that expects that exact type without an explicit conversion.
Clone creates an ownership boundary for element slots
A common reason to clone a slice is to stop callers from replacing elements through shared slice storage. Consider a constructor that retains a supplied slice:
type Registry struct {
names []string
}
func NewRegistry(names []string) *Registry {
return &Registry{
names: slices.Clone(names),
}
}After construction, replacing an element in the caller’s names slice does not replace an element in Registry.names. The constructor has taken a snapshot of the string elements at that moment.
This boundary is limited to the element values themselves. If the slice held pointers or maps, cloning the outer slice would not isolate mutations made through those values. A deeper copy requires type-specific code that defines which nested data must also be duplicated.
Capacity is not the contract to depend on
The useful guarantee from Clone is element copying with the same length and preserved nil state. Code should not treat a particular spare capacity as part of the API contract.
If later appends need a known amount of headroom, capacity planning is a separate concern. slices.Grow expresses an append-capacity requirement, while slices.Clone expresses a copy of existing slice elements. Keeping those operations conceptually separate makes storage intent visible in the code.
A clone isolates slots, not every reachable value
slices.Clone fits code that needs a new slice containing the current element values without retaining shared element slots. It preserves the slice type and nil state, and it avoids hand-written make plus copy sequences for that operation.
The boundary remains shallow. For slices whose elements point to mutable data, deciding whether that nested data also needs duplication is a separate ownership decision that Clone intentionally leaves to the caller.