Appending to a slice I passed to a function changed the caller's data — I thought slices were passed by value
I have a function that takes a slice, appends a couple of elements and returns the result. The caller keeps its own slice and uses the returned one separately.
Sometimes the caller's original slice has changed underneath it. Not always — and that is what is bothering me, because a bug that happens sometimes is worse than one that always happens.
My mental model was that slices are passed by value, so the function gets its own copy and append gives a new slice. That is clearly wrong somewhere.
The worst part is that it is fine in tests with small inputs and wrong in production. Can someone explain what is actually happening, and what the correct pattern is when a function takes a slice it might append to?
@copy_it_carla · 5h ago
The rule that avoids the whole category: decide who owns the backing array, and write it down.
In practice that means one of these, chosen deliberately:
The function takes ownership. Document it — "appends to s and returns it; s must not be used afterwards" — which is exactly the contract
appenditself has. This is the cheapest and it is idiomatic; the reasonappendreturns a slice at all is to make the caller reassign and stop using the old one.The function copies before touching anything. If you cannot guarantee the caller will stop using its slice, copy first.
out := make([]T, len(s), len(s)+n)thencopy(out, s). Now nothing you do is visible outside. Costs an allocation, and it is worth it at any boundary you do not control.The function only reads. Then take the slice as-is and never append to it. If you might grow it, you are in one of the first two cases.
The common bug is the accidental fourth option — appending to a caller's slice without saying so and hoping capacity happens to be zero.
Worth knowing this bites hardest with subslices.
s[:2]has the capacity of the original from that offset, so appending to it overwrites elements of the parent that are past index 2. That one surprises people even after they understand the rest, because the subslice looks small and self-contained.Reply
Report