Rent-a-founder

Golang: Append Modifies Underlying Slices

Even after four years of programming in Go, there are still things I didn’t know about the language itself. The following example illustrates behaviour that surprised me. My (false) assumptions actually led to a bug which was very difficult to find as it happened only rarely and repeated reviews of the code didn’t turn up anything unusual. Here it is:

package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3}
	b := append(a[:2], 4)
	fmt.Println(a)
	fmt.Println(b)
}

One would think that the output would be:

[1 2 3]
[1 2 4]

But it’s actually:

[1 2 4]
[1 2 4]

The a slice is also modified. So append does not allocate a new slice but overwrites the third element of the original slice with the new value (4). It’s not a surprise if you read the specification thoroughly:

Otherwise, append re-uses the underlying array.

The “otherwise” in this sentence is the critical part. Because if there is no space to append the new elements, a new slice is allocated and a is indeed left untouched:

package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3}
	b := append(a[:2], 4, 5)
	fmt.Println(a)
	fmt.Println(b)
}

The output here is:

[1 2 3]
[1 2 4 5]

For this blog post, I thought I’d justify my false assumption by pointing out that a language like Javascript behaves differently:

var a = [1,2,3],
  b = a.slice(0,2).concat(4);
console.log(a);
console.log(b);

But slice will always allocate a new array so the result is expected:

[1, 2, 3]
[1, 2, 4]

So maybe I should have seen this coming. But I think append’s actual behaviour here may be surprising to some Go developers as it was to me so I’d say it warrants its own blog post.