I'm reading 50 shades in Go, specifically Iteration Variables and Closures in "for" Statements, and I'm going to take an excerpt from it.
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{{"one"},{"two"},{"three"}}
for _,v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
//goroutines print: three, three, three
}
Change []field{{"one"},{"two"},{"three"}}
to []*field{{"one"},{"two"},{"three"}}
, and one
, two
, and three
will be printed in some order.
In the incorrect one, go v.print()
is replaced by go (&v).print()
by compiler because print()
is defined on pointer receiver.
Until the spawned goroutine is executed, runtime only knows that goroutine should executeprint()
, but has no idea pointer of which instance should be passed as the receiver.
When the spawned goroutine is executed, it's highly possible that the for loop has terminated, so when we want to decide which value should be passed as the receiver, we get the address of v
, which is shared during the entire loop and updated in each iteration, so we pass the address of the last element of data
as the receiver to print()
, and that's why we get 3 three
printed.
To me, changing []field
to []*field
only lets compiler skip the step 1, but does not change step 2 and step 3, so I don't know why that fixes the problem.
I guess there must be some flaws in my thought process, and I appreciate any advice.
I happened to see another correct implementation here, and I think I might know where went wrong in my thought process.
data := []field{{"one"}, {"two"}, {"three"}}
for i := range data {
go data[i].print()
}
The thing is, the pointer to be passed to print()
as the receiver, is determined in step 2 instead of step 3. That means in the incorrect version, we're passing the same address in each iteration, but the content it points to (data
) is updated in each iteration. However, in the correct version, the pointers being passed to print()
as the receiver, point to the actual elements of field
. The same applies to the case using indices.
Your receiver is a poniter and you have to define your field slice as pointer this is why this code works
data := []*field{{"one"}, {"two"}, {"three"}}
if you change your receiver to non pointer you code works too .
func (p field) print() {
fmt.Println(p.name)
}
data := []field{{"one"}, {"two"}, {"three"}}