Beware of array reuse with append

September 2, 2024

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)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Efficiently appending to slices

Yesterday we were introduced to the append function, which on the surface seems quite straight forward. But there are nuänces and gotchas! Appending to and copying slices … If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. You may recall from our (much) earlier discussion about slices and backing arrays, that a slice has not only a length (the number of elements it contains), but also a capacity—essentially the number of available elements in the backing array.


Appending to slices

Appending to and copying slices The built-in functions append and copy assist in common slice operations. For both functions, the result is independent of whether the memory referenced by the arguments overlaps. Today and tomorrow we’ll look at the first of those two, append: The variadic function append appends zero or more values x to a slice s and returns the resulting slice of the same type as s. The core type of s must be a slice of type []E.


Clear

Clear The built-in function clear takes an argument of map, slice, or type parameter type, and deletes or zeroes out all elements [[Go 1.21(https://go.dev/ref/spec#Go_1.21)]]. Call Argument type Result clear(m) map[K]T deletes all entries, resulting in an empty map (len(m) == 0) clear(s) []T sets all elements up to the length of s to the zero value of T clear(t) type parameter see below clear is a relatively recent addition to Go, added just over a year ago.

Get daily content like this in your inbox!

Subscribe