On Friday we looked at what happens when appending to a slice exceeds the capacity of the backing array.
Today we’ll consider the alternative: What if the backing array can hold the new values?
Appending to and copying slices
… Otherwise, append re-uses the underlying array.
Okay.
That sounds pretty simple, doesn’t it?
What’s to talk about?
Well, this single sentence packs a lot of surprises.
Consider this code:
a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s := a[0:3]
s = append(s, 10, 20, 30)
fmt.Println(a)
Can you guess what this code prints?
[1 2 3 10 20 30 7 8 9]
WUT?
How does adding the values 10
, 20
, and 30
to s
affect the values of a
?
Remember: a slice is a sort of window, or view, into an array. And the way we defined our slice s
, we made it reference the first three elements of our array a
. So when we append to s
, just as the spec says: “append re-uses the underlying array”. So the new values added to s
replace the existing values in the same positions in a
.
What can you do about this?
In many cases, you don’t need to care at all.
But any time you slice an array (or other slice), be concious of what that means. If it’s important that your backing array is not modified by append
(or other operations), use a copy.
s0 := []int{0, 0} s1 := append(s0, 2) // append a single element s1 is []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // append multiple elements s2 is []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // append a slice s3 is []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, "foo") // t is []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // append string contents b is []byte{'b', 'a', 'r' }
Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)