Change struct field through pointer in map

Hi.

I can’t manage to set a struct field through a pointer in a map. The last two print statements should print 44.

Thanks

1 Like

Hi @jonasgheer,

This is indeed a tricky one.

In the range loop

	for _, r := range robots {
		robotMap[r.name] = &r
	}

r is a copy of the slice entry.

To get the slice entry itself, use the loop index:

	for i, r := range robots {
		robotMap[r.name] = &robots[i]
	}
2 Likes

Thank you! Do you know where I can find some documentation or similar that would explain this in more detail? “Copy of slice entry” still does not make sense to me. Isn’t the slice entry a pointer?

robots is defined as robots := []Robot{Robot{"Ronny", 33}} so its type is []Robot.

When you iterate over robots here:

for _, r := range robots { /* ... */ }

r is a Robot, not a *Robot. If you wanted the address of the element, then you have to do:

for i := range robots {
    r := &robots[i]
    /* ... */
}

So when you take the address of r, it forces r to escape to the heap and turn into its own separate value in memory. When you mutate it later, you’re mutating the copy.

I have to admit, this question stumped me! I was also surprised to see what happens when you add another entry to the slice initially: The Go Play Space

I also never heard of the Go Play Space!

Thanks for posting, @jonasgheer, and awesome catch, @christophberger!

2 Likes

Entries of []robots are of type robot, and this type is what the range iterator returns.

The language reference does not directly talk of copying, but the “For statements with range clause” subsection of section For statements says,

A “for” statement with a “range” clause iterates through all entries of an array, slice, string or map, or values received on a channel. For each entry it assigns iteration values to corresponding iteration variables if present and then executes the block.

Assignment is a copy operation by definition. Hence the content of r in for _, r := range... is a copy of the current slice element.

Here is another explanation. (Shameless plug: this is from my course Master Go.)


There is a possible pitfall when using the range operator. The element value that the range operator returns on each iteration is only a copy of the element within the list object. So if, for example, we try to replace a character in a string, assigning a new value to the element variable is not enough.

s := []int{1, 2, 3}  // A slice of integers, initialized with three values
for _, element := range s {
    element = 4
    fmt.Println(element)
}
fmt.Println(s)

Output:

4
4
4
[1 2 3]

The output shows that element is set to 4 at each iteration, but after the loop, the byte slice remains unchanged.

In order to modify the contents of the slice, we need to use the loop index.

s := []int{1, 2, 3}
for index, _ := range s {
    s[index] = 4
}
fmt.Println(s)

When we run this, we can see that all slice values get modified.

4
4
4
[4 4 4]
1 Like