According to the builtin api docs, append() will reallocate and copy to a new array block when the capacity of the original slice is not large enough.
Here is a (simplified version of) a recursive algorithm for creating combinations of an alphabet (in this case booleans). Members of the alphabet (true, false) are recursively added to a slice until it is the correct length, at which point it is sent over the channel.
package main
import (
"fmt"
)
func AddOption(c chan []bool, combo []bool, length int) {
if length == 0 {
fmt.Println(combo, "!")
c <- combo
return
}
var newCombo []bool
for _, ch := range []bool{true, false} {
newCombo = append(combo, ch)
AddOption(c, newCombo, length-1)
}
}
func main() {
c := make(chan []bool)
go func(c chan []bool) {
defer close(c)
AddOption(c, []bool{}, 4)
}(c)
for combination := range c {
fmt.Println(combination)
}
}
Here is the playground link to this code. In the output:
[true true true true] !
[true true true false] !
[true true true false]
[true true true false]
[true true false true] !
[true true false false] !
[true true false false]
[true true false false]
[true false true true] !
[true false true false] !
[true false true false]
[true false true false]
[true false false true] !
[true false false false] !
[true false false false]
[true false false false]
[false true true true] !
[false true true false] !
[false true true false]
[false true true false]
[false true false true] !
[false true false false] !
[false true false false]
[false true false false]
[false false true true] !
[false false true false] !
[false false true false]
[false false true false]
[false false false true] !
[false false false false] !
[false false false false]
[false false false false]
Lines ending in an exclamation mark are those sent into the channel from AddOption. Those without are what emerges on the other side (i.e. in main()). It is clear that the slices send over the channel are changed after they are sent.
Since AddOption returns immediately after sending the slice, the modification has to come from the code block
var newCombo []bool
for _, ch := range []bool{true, false} {
newCombo = append(combo, ch)
AddOption(c, newCombo, length-1)
}
But, according to the docs, append() should return a new slice (cap(combo) is not large enough). According to this answer, the slice descriptor sent to AddOption should be a copy; is that not true? As far as I can tell, either the value sent as the second argument to AddOption() is either a pointer to a slice descriptor, or append() is not returning a new slice.
When
append()
creates a new slice, it doesn't create a slice that's just one larger than the slice before. It actually creates a slice that is already a couple of elements larger than the previous one. Have a look at this code:Playground
If you run this code, you see that the capacity initially doubles on every allocation; this strategy is of course changed for larger slice sizes.
Ref: http://criticalindirection.com/2016/02/17/slice-with-a-pinch-of-salt/
According to the link:
This is quite different from the behavior of slices in other languages:
The output of example mentioned, explains the behavior.
Having multiple slices pointing to same underlying array, with frequent
append
operations can get tricky. More on this in the link above.You are confusing slice, the data type, with the actual representation. The slice descriptor is composed of a pair of ints, one for len and one for cap, and a pointer to the underlying data.
So, what append returns is indeed a new slice and what is passed to add option is indeed a copy of the slice descriptor. But since the descriptor has a pointer to data, the pointer value (the address to the underlying data) is the same.
EDIT: Here is a code snippet to illustrate my point:
If you run this, you get:
Because since
s
still has capacity, botha
andb
share the same data ptr. If you change the capacity to 4, it prints: