append()如何在导致这种情况的幕后工作?

I'm pretty new to Go and trying to track down a bug in my codebase. In the process I've distilled the problem down to a single call to append(), but can't figure out why it's behaving the way it is...

func main() {
    foo := []string{"a", "b", "c"}
    fmt.Printf("before: %v
", foo)

    i := 0
    noop(append(foo[:i], foo[i+1:]...)) // -- call append, but do nothing with the result
    fmt.Printf(" after: %v
", foo)
}

func noop(a interface{}) {} // -- avoid "evaluated but not used" errors

Try it here

So, what the heck is really going on here?

append(foo[:i], foo[i+1:]...) does the following:

  1. It takes the foo[:i] slice, which is foo[:0] and basically a slice with length 0 and capacity (at least) 3.

  2. As soon as the capacity is enough to append the values - the underlying array is reused

  3. You write b and c into the indexes 0 and 1 correspondingly of the underlying array.

Then you check the foo variable that uses the underlying array we just modified and that contains the b c c values.

Compare with the following:

noop(append(foo[:i], "a", "a", "a", "a", "a"))

Here the list of values to append is longer than the current capacity. So the runtime allocates a new underlying array. And you don't mutate the foo. https://play.golang.org/p/RooYG_p9Z8

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. Otherwise, append re-uses the underlying array.

Breaking it down, s here is foo[:0], which has a capacity of at least 3, since the length of foo is 3. s has 0 elements, and append wants to add two elements, [b c], so there is room, and append will re-use the underlying array of foo[:0] which is the same as the underlying array of foo.

So it places the items [b c] at the beginning of the array, and returns a new slice of that array with a length of 2.

But you're still looking at foo, which is a slice of that same array, but still has a length of 3; the third item is the c which hasn't been touched, so foo is now [b c c].

I'm also relatively new to go, but the way I understand append is that it appends all the following arguments to the first argument.

So essentially what you are doing is appending foo[1] and foo[2] to foo[0] and foo[1] without actually changing the length of foo.

If you want to preserve the original slice what you want is this