Strange golang "append" behavior (overwriting values in slice)
Because you are appending the slice using as a pointer reference actually you are not creating new entries in the slice but updating every time the last entry. Changing the code from pointer reference to normal value reference it will work.
package main
import "fmt"
type Foo struct {
val int
}
func main() {
var a = make([]Foo, 1)
a[0] = Foo{0}
var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
for _, e := range b {
a = append(a, e)
}
for i, e := range a {
fmt.Printf("%d: %v\n", i, e)
}
}
Working code on Go playground.
This is because in the for
loop you operate with a copy and not with the slice/array element itself.
The for ... range
makes a copy of the elements it loops over, and you append the address of this temporary, loop variable - which is the same in all iterations. So you add the same pointer 3 times. And this temporary variable will be set to Foo{3}
in the last iteration (last element of the array), so that's why you see that printed 3 times.
Fix: do not add the address of the loop variable, but the address of the array element:
for i := range b {
a = append(a, &b[i])
}
Output (try it on the Go Playground):
{0} {1} {2} {3}
See possible duplicate Assigned pointer field becomes <nil>.
Reasoning for this behavior
In Go there are pointer types and non-pointer types, but no "references" (in the meaning it is used in C++ and Java). Given the fact that there are no "reference" types in Go, this is not an unexpected behavior. The loop variable is just an "ordinary" local variable, it can only hold a value (which may be a pointer or non-pointer), but not a reference.
Excerpt from this answer:
Pointers are values just like let's say
int
numbers. The difference is the interpretation of that value: pointers are interpreted as memory addresses, andint
s are interpreted as integer numbers.When you want to change the value of a variable of type
int
, you pass a pointer to thatint
which is of type*int
, and you modify the pointed object:*i = newvalue
(the value assigned is anint
).Same goes with pointers: when you want to change the value of a variable of pointer type
*int
, you pass a pointer to that*int
which is of type**int
and you modify the pointed object:*i = &newvalue
(the value assigned is an*int
).
All in all, the loop variable is just a normal variable having the element type of the array/slice you're looping over, and for it to have the value of the actual iteration, the value must be assigned to it which copies the value. It is overwritten in the next iteration.
On each iteration of your loop, the value of e
changes, yet each time you're passing a pointer to e
into the slice. So you end up with a slice containing 3 pointers to the same value.
You can also make a copy of the value and pass its pointer. Since on each iteration, you're making a new copy of the value, the pointers passed to the slice will point to different values:
var a = make([]*Foo, 1)
a[0] = &Foo{0}
var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
for _, e := range b {
eCopy := e
a = append(a, &eCopy)
}
https://play.golang.org/p/VKLvNePU9af