When using loop, result is set as only last value


(Robert Sung Wook Shin) #1

Hello folks,

From below code, map is set different value unlike my expect.

Is there somthing that I lost?

package main

import (
	"fmt"
)

func main() {
	mySlice := []string{"1st", "2nd", "3rd"}
	fmt.Println(mySlice)
	
	newMap := make(map[string]*string)
	
	for key, item := range mySlice {
		switch currentItem := item; currentItem {
		case "1st":
			// newMap[string(key)] = &mySlice[key] // set value which I expected
			newMap[string(key)] = &item // set value which last in slice

			fmt.Println("Added", currentItem)
		default:
			fmt.Println("Not added", currentItem)
		}
	}
	
	fmt.Print("\nResult using value:")
	for k, v := range newMap {
		fmt.Println(k, *v)
	}
}

https://play.golang.org/p/m7HzMDTi_CG


(Curtis Allyn Green) #2

Seems like some issue with slice representation and mapping to underlying memory addresses. I’m not sure why its giving you the third item other than your map being string pointers and a slice is already a set of pointers to underlying array. If you look at this example of your code without mapping string pointers it works as expected: https://play.golang.org/p/u9kJdZTAA8i

I hope this helps you determine the problem, and maybe someone else can explain this behavior better.


(Robert Sung Wook Shin) #3

Hello @CurtGreen,

Unfortunately, I’m using struct instead of string.

Actual code which is almost same with my works is here.

package main

import (
	"fmt"
)

type mySet struct {
	Indicator	string
	Value		string
}

func main() {
	mySlice := []mySet{
		mySet {
			Indicator: "new",
			Value: "1st-value",
		},
		mySet {
			Indicator: "change",
			Value: "2nd-value",
		},
		mySet {
			Indicator: "delete",
			Value: "3rd-value",
		},
	}
	fmt.Println(mySlice)
	
	newMap := make(map[string]*mySet)
	
	for key, item := range mySlice {
		fmt.Println("Where is written: ", item.Value, &item.Value)

		switch item.Indicator {
		// Problem is "new"
		case "new":
			newMap["0"] = &mySlice[key]	// set value which I expected
			// newMap["0"] = &item		// set value which last in slice

			fmt.Println("Added", item.Indicator, key)
		case "change":
			newMap["0"].Value = item.Value

			fmt.Println("Changed value of the new item", item.Indicator, key)
		default:
			fmt.Println("Not added", item.Indicator, key)
		}
	}
	
	fmt.Print("\nResult using value:")
	for k, v := range newMap {
		fmt.Println(k, v)
	}
}

https://play.golang.org/p/dU8HYZ3lO8q

Maybe this result caused from same memory space which is used by loop.

Thank you for kind answer.


(Ignacio Gómez) #4

Don’t try to get the address of some var from a range loop inside the loop. When ranging, on every iteration the value of the item is copied to the given range identifier and that’s what’s used inside the loop. Instead, use and old for i := 0... loop to effectively get the elements of the array/slice/etc.

Here’s some more info: https://github.com/golang/go/wiki/Range.


(Robert Sung Wook Shin) #5

More question.

I tried to add item re-assigning in loop then I found that the code works quite well regardless memory space.

	for key, item := range mySlice {
		item := item	// Why work with this???
		fmt.Println("Where is written: ", item.Value, &item.Value)

https://play.golang.org/p/qQed8V0rIJT

But I wonder why works and whether this is safe or not.

Is it - variable reassigning, enough to be a solution in this case?


(Ignacio Gómez) #6

In the first case (without declaring and assigning an inner variable), the variable item is always the same and gets assigned a copy of the element in each iteration, so setting newMap["0"] with a pointer to item will effectively leave newMap["0"] set to the last element in the iteration, i.e. {delete 3rd-value}, because it points to item.

If instead you assign item to a new variable in the loop, and then assign a pointer to that new variable to the map, then the third iteration won’t affect what’s in newMap["0"], as it points to a different address, not that of the range’s item variable but of the inner declared one.

Look at this example (or check it on the playground at https://play.golang.org/p/8VWEPADY29v):

package main

import (
	"fmt"
)

func main() {
	a := [3]int{1, 2, 3}
	fmt.Printf("array addresses: %p, %p, %p\nvalues: %d, %d, %d\n\n", &a[0], &a[1], &a[2], a[0], a[1], a[2])
	var b *int
	for i, v := range a {
		if i < 1 {
			b = &v
		}
	}
	fmt.Printf("b address: %p\nvalue: %d\n\n", b, *b)

	for _, v := range a {
		c := &v
		*c = *c + 1
		fmt.Printf("c address: %p\nvalue: %d\n\n", c, *c)
	}

	for _, v := range a {
		c := v
		c = c + 1
		fmt.Printf("c address: %p\nvalue: %d\n\n", &c, c)
	}

	fmt.Printf("array addresses: %p, %p, %p\nvalues: %d, %d, %d\n", &a[0], &a[1], &a[2], a[0], a[1], a[2])

	for i := 0; i < len(a); i++ {
		a[i]++
	}

	fmt.Printf("array addresses: %p, %p, %p\nvalues: %d, %d, %d\n", &a[0], &a[1], &a[2], a[0], a[1], a[2])
}

The output will look like this:

array addresses: 0x416020, 0x416024, 0x416028
values: 1, 2, 3

b address: 0x416038
value: 3

c address: 0x416040
value: 2

c address: 0x416040
value: 3

c address: 0x416040
value: 4

c address: 0x416050
value: 2

c address: 0x416058
value: 3

c address: 0x416060
value: 4

array addresses: 0x416020, 0x416024, 0x416028
values: 1, 2, 3
array addresses: 0x416020, 0x416024, 0x416028
values: 2, 3, 4

Here you can see how b is assigned a pointer to v only on the first iteration, but its final value is that of a[2], as v is just one variable that gets copied the elements of a on each iteration, and b is pointing to it so it will also change.

The second loops explicitly shows that v is just one variable, as taking its address always prints the same.

The last loop shows that when declaring and assigning v to a new variable c, on each iteration c is a new variable with a different address.

Finally, you can see how all of this didn’t affect the array a, as its elements were always copied in the range loop, while doing it with a plain for loop allows to change the elements.