range clause and the address of iteration variable

Michał Łowicki
golangspec
Published in
2 min readJul 6, 2018

(source code)

package mainimport (
"fmt"
)
type T struct {
id int
}
func main() {
t1 := T{id: 1}
t2 := T{id: 2}
ts1 := []T{t1, t2}
ts2 := []*T{}
for _, t := range ts1 {
ts2 = append(ts2, &t)
}
for _, t := range ts2 {
fmt.Println((*t).id)
}
}

Think for a moment about expected output of this program..

For some (including me) the result might be surprising at first glance:

2
2

I’ve personally expected

1
2

but it turned out to be wrong answer.

Iteration variable t is declared using short variable declaration. Its scope is the block of the “for” statement. It’s a variable which during first iteration hold value of the first element and during 2nd iteration value of the second element . But it’s just another place in memory which hold current element of slice being iterated over. It doesn’t point to values stored inside slice’s underlying array — it’s a temporary bucket where next elements are copied over. So &t will have the same value in each iteration since it’s a auxiliary variable to hold currently iterated element.

(source code)

t1 := T{id: 1}
t2 := T{id: 2}
ts1 := []T{t1, t2}
ts2 := []*T{}
for _, t := range ts1 {
t.id = 3
ts2 = append(ts2, &t)
}
for _, t := range ts2 {
fmt.Println((*t).id)
}
fmt.Println(ts1)
fmt.Println(t1)
fmt.Println(t2)

output:

3
3
[{1} {2}]
{1}
{2}

Possible solution is to use index and get the address of slice’s element (source code):

t1 := T{id: 1}
t2 := T{id: 2}
ts1 := []T{t1, t2}
ts2 := []*T{}
for i, _ := range ts1 {
ts2 = append(ts2, &ts1[i])
}
for _, t := range ts2 {
fmt.Println((*t).id)
}

output:

1
2

Resources

https://golang.org/ref/spec#For_range

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.