Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange golang "append" behavior (overwriting values in slice)

I have this simple code:

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 _, e := range a {
        fmt.Printf("%v ", *e)
    }
}

I was expecting it to print {0} {1} {2} {3}, however it prints {0} {3} {3} {3}. What happened here?

like image 920
Chao Sun Avatar asked Aug 01 '16 07:08

Chao Sun


Video Answer


3 Answers

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, and ints are interpreted as integer numbers.

When you want to change the value of a variable of 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).

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.

like image 56
icza Avatar answered Oct 19 '22 21:10

icza


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

like image 3
Sam Malayek Avatar answered Oct 19 '22 19:10

Sam Malayek


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.

like image 1
Endre Simo Avatar answered Oct 19 '22 19:10

Endre Simo