While doing the go track I realized after a while I had misunderstood how append handles slices, this was due to the following sentence in the slices syllabus:
After looking into this, it seems a more appropriate text would be something like:
The append function in Go may modify the underlying array of a slice for performance reasons, rather than allocating a new array. This means the data represented by the original slice can change, even though the original slice descriptor itself (its length, capacity, and pointer) remains unchanged unless reassigned.
After having a quick look I think there are two files that this should be changed in. Do you want me to open a PR and propose a change?
I’m not really an expert in Go, but to me the two statements seem to say the same thing. Essentially the append function can mutate the input in certain cases, so it’s a bit of a ‘gotcha’ moment.
So, I don’t see the need to change.
Also, neither of the two statements really does a great job explaining why this behaviour exists and what triggers it.
Ok, let’s drop my proposed change and discuss the current text instead.
Current statement says: "append can sometimes modify the original slice".
This is false, the append function never modifies the original slice. The append function creates a new slice and returns it. The new slice may or may not be a view of the same array as the original slice is a view of.
The definition of a slice: “is a dynamically-sized, flexible view into the elements of an array. (A Tour of Go)”.
To summarize: The append function can not mutate the input (the slice) but it can mutate the array that the slice “is a view of”.
I’m new to go, so I might be wrong here, so feel free to correct me
[…] If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. […]
Good point, I think this is described elsewhere, but here is an example of why and what triggers it:
primesArray := [3]int{2, 3}
sliceOne := primesArray[0:2]
fmt.Println(primesArray) // outputs: [2 3 0]
fmt.Println(sliceOne) // outputs: [2 3]
// Append adds an element to the underlying array, since it has space.
// We can see that the array is changed and that the original slice is not changed,
// it still outputs [2 3] while the new returned slice outputs [2 3 5]
sliceTwo := append(sliceOne, 5)
fmt.Println(primesArray) // outputs: [2 3 5]
fmt.Println(sliceOne) // outputs: [2 3]
fmt.Println(sliceTwo) // outputs: [2 3 5]
// Since the array has no more space, append needs to allocate a new array.
// We can see that the first array is not modified
sliceThree := append(sliceTwo, 7)
fmt.Println(primesArray) // outputs: [2 3 5]
fmt.Println(sliceOne) // outputs: [2 3]
fmt.Println(sliceTwo) // outputs: [2 3 5]
fmt.Println(sliceThree) // outputs: [2 3 5 7]
Hm, perhaps I’m missing something here, but what is wrong with my statement?
When the Go documentation says “the destination is resliced,” I think it means that a new slice descriptor is created. This new descriptor may point to the same underlying array (if there’s enough capacity), but it’s still a separate object.
Seems like this was quite an interesting topic as we struggle to understand each other
Nothing is wrong with your statement, I just find it more confusing than the original statement, so I suggested that the change is unnecessary.
I am not well versed in Go, so it is entirely possible that you’re more correct than I am in what you’re saying, or that we’re both misinterpreting the docs, so I’ll let someone that is more experienced than me talk here.
My suggestion for change, let me know if this helps with the original confusion:
The append function of Go is optimized for performance and therefore does not make a copy of the data of input slice. However, the slice itself contains other information other than the data, like the length and capacity of the slice. Since that information changes when you perform an append, we say that a new slice is created, despite no copies of the actual data the slice holds being made. This new slice with slightly modified metadata is returned from append. And this is also the reason why it’s a good idea to assign the return of the append call - that way you ensure you always have a reference to the most up-to-date slice.
What I like most about this description vs the suggested one is that it tries to be slightly less technical and focus on how this details affects you writing code, rather than focusing on being 100% technically correct.