Golang-For-Loop

πŸ˜€

Recently my colleague meets a problem with golang for loop. He ranged an array of User, wanted to filter some user and append them (use pointer to get their address) to another user array, but finally got an array contains the same user.

Some thing like this:

newUsers := []*User{}
for _, u := range UserArray{
	if checkFailed(u){
		continue
	}
	newUsers = append(newUsers, &u)
}

The newUsers got the same user with different indexes.

If you didn’t read the full documents of golang, you may think it’s unbelievable just like I did. But the truth is, for-loop will not allocate new address for the variable, it just reuse the address and pass the new value to it. So anything you do with that value will not infect the original one unless the value itself is a pointer. Also, if you want get the address of the value, you will always get the same since for-loop use single address.

Let’s make some code to prove it.

(You can get it in here: https://u.kfd.me/2l)

package main

import "fmt"

func main() {
	fmt.Println("test array")
	array := []int{1, 2, 3, 4, 5}
	xArray := []*int{}
	iArray := []*int{}
	for i, x := range array {
		xArray = append(xArray, &x)
		iArray = append(iArray, &array[i])
	}
	for _, p := range xArray {
		fmt.Printf("[x] p: %p, v: %d\n", p, *p)
	}
	for _, p := range iArray {
		fmt.Printf("[i] p: %p, v: %d\n", p, *p)
	}

	fmt.Println("test pointer array")
	iiArray := []*int{}
	xxArray := []*int{}
	// with pointer
	for i, p := range iArray {
		xxArray = append(xxArray, p)
		iiArray = append(iiArray, iArray[i])
	}
	for _, p := range xxArray {
		fmt.Printf("[xx] p: %p, v: %d\n", p, *p)
	}
	for _, p := range iiArray {
		fmt.Printf("[ii] p: %p, v: %d\n", p, *p)
	}

	fmt.Println("test map")
	one := 1
	two := 2
	mapp := map[int]*int{
		1: &one,
		2: &two,
	}
	xMap := map[int]*int{}
	iMap := map[int]*int{}
	for k, v := range mapp {
		fmt.Printf("address of mapp[%d]: %p\n", k, v)
		xMap[k] = v
		iMap[k] = mapp[k]
	}
	for k, v := range xMap {
		fmt.Printf("[x] k: %d, v: %d, p: %p\n", k, *v, v)
	}
	for k, v := range iMap {
		fmt.Printf("[i] k: %d, v: %d, p: %p\n", k, *v, v)
	}
}

Result:

test array
[x] p: 0x416030, v: 5
[x] p: 0x416030, v: 5
[x] p: 0x416030, v: 5
[x] p: 0x416030, v: 5
[x] p: 0x416030, v: 5
[i] p: 0x45a000, v: 1
[i] p: 0x45a004, v: 2
[i] p: 0x45a008, v: 3
[i] p: 0x45a00c, v: 4
[i] p: 0x45a010, v: 5
test pointer array
[xx] p: 0x45a000, v: 1
[xx] p: 0x45a004, v: 2
[xx] p: 0x45a008, v: 3
[xx] p: 0x45a00c, v: 4
[xx] p: 0x45a010, v: 5
[ii] p: 0x45a000, v: 1
[ii] p: 0x45a004, v: 2
[ii] p: 0x45a008, v: 3
[ii] p: 0x45a00c, v: 4
[ii] p: 0x45a010, v: 5
test map
address of mapp[1]: 0x416084
address of mapp[2]: 0x416088
[x] k: 1, v: 1, p: 0x416084
[x] k: 2, v: 2, p: 0x416088
[i] k: 1, v: 1, p: 0x416084
[i] k: 2, v: 2, p: 0x416088

As we can see, in our first try, xArray got the same value and the same address while the iArray got the different value and the real address of the original array. So, in a for-loop, the second value (besides pointer, actually the pointer is also a value-copy but since it’s a pointer, there is no difference) is a value-copy, if you want to get the real address, use the index of that array, not the second value.


Last but not least, this article described this problem deep and full, please do read this if you are interested in the for-loop:

Go Range Loop Internals

comments powered by Disqus