Methods with value vs. pointer receivers (JNNC Technologies)

                                                       https://jnnctechnologies.com/

A similar artifact can be observed when creating goroutines that invoke methods. This is even pointed out explicitly on the CommonMistakes page. Consider example 3:
type MyInt int

func (mi MyInt) Show() {
  fmt.Println(mi)
}

func main() {
  ms := []MyInt{50, 60, 70, 80, 90}
  for _, m := range ms {
    go m.Show()
  }

  time.Sleep(100 * time.Millisecond)
}
This prints the elements of ms, possibly in a scrambled order, as you'd expect. A very similar example 4 uses a pointer receiver for the Show method:
type MyInt int

func (mi *MyInt) Show() {
  fmt.Println(*mi)
}

func main() {
  ms := []MyInt{50, 60, 70, 80, 90}
  for _, m := range ms {
    go m.Show()
  }

  time.Sleep(100 * time.Millisecond)
}
Can you guess what the output is going to be? It's 90 repeated five times. The reason is exactly the same as in the simpler example 2. Here it's a bit more insidious because of the syntactic sugar Go applies to calling methods with pointer receivers. Whereas between examples 1 and 2 we changed the argument passed into the function from i to &i, here the method invocation is exactly the same! It's go m.Show() in both cases, yet the behavior is very different.
This is a rather unfortunate confluence of Go features, IMHO. Nothing in the call site hints that m is passed by reference, and one has to go look at the documentation of the Show method to see that (the method can be in a completely different package from the calling code, of course). In most cases this feature is useful, enabling us to write cleaner code. But here the passing by reference has unexpected effects.

Closures

Finally we come back to closures, with example 5:
func foobyval(n int) {
  fmt.Println(n)
}

func main() {
  for i := 0; i < 5; i++ {
    go func() {
      foobyval(i)
    }()
  }

  time.Sleep(100 * time.Millisecond)
}
This prints:
5
5
5
5
5
Even though i is passed by value to foobyval in the the closure, which seems like it should be fine based on example 1. Let's find out why. We'll start with the disassembly of the for loop:
0x0039 00057 (go-closure.go:14) MOVL    $8, (SP)
0x0040 00064 (go-closure.go:14) LEAQ    "".main.func1·f(SB), CX
0x0047 00071 (go-closure.go:14) MOVQ    CX, 8(SP)
0x004c 00076 (go-closure.go:14) MOVQ    AX, 16(SP)
0x0051 00081 (go-closure.go:14) CALL    runtime.newproc(SB)
0x0056 00086 (go-closure.go:13) MOVQ    "".&i+24(SP), AX
0x005b 00091 (go-closure.go:13) INCQ    (AX)
0x005e 00094 (go-closure.go:13) CMPQ    (AX), $5
0x0062 00098 (go-closure.go:13) JLT     57
It's fairly similar to example 2: note that i is represented as an address in the AX register, meaning that we pass it around by reference, even though the closure calls foobyval. This loop body invokes a function using runtime.newproc, but where does this function come from?
func1 is created by the compiler to represent the closure. The compiler outlines the closure's code into a standalone function and inserts a call to it in main. The main challenge in outlining closures like this is how to treat the variables the closure implicitly uses but weren't declared in its argument list.
Here's the body of func1:
0x0000 00000 (go-closure.go:14) TEXT    "".main.func1(SB), ABIInternal, $16-8
0x0000 00000 (go-closure.go:14) MOVQ    (TLS), CX
0x0009 00009 (go-closure.go:14) CMPQ    SP, 16(CX)
0x000d 00013 (go-closure.go:14) JLS     56
0x000f 00015 (go-closure.go:14) SUBQ    $16, SP
0x0013 00019 (go-closure.go:14) MOVQ    BP, 8(SP)
0x0018 00024 (go-closure.go:14) LEAQ    8(SP), BP
0x001d 00029 (go-closure.go:15) MOVQ    "".&i+24(SP), AX
0x0022 00034 (go-closure.go:15) MOVQ    (AX), AX
0x0025 00037 (go-closure.go:15) MOVQ    AX, (SP)
0x0029 00041 (go-closure.go:15) CALL    "".foobyval(SB)
0x002e 00046 (go-closure.go:16) MOVQ    8(SP), BP
0x0033 00051 (go-closure.go:16) ADDQ    $16, SP
0x0037 00055 (go-closure.go:16) RET
The interesting thing to note is that the function has an argument in 24(SP), which is an int pointer - note the MOVQ (AX), AX which extracts its value before passing it to foobyval. In essence, func1 looks something like this:
func func1(i *int) {
  foobyval(*i)
}
And the loop in main is transformed into:
for i := 0; i < 5; i++ {
  go func1(&i)
}
This is equivalent to example 2, which explains the output of the program. The technical jargon for what's happening here is that i is a free variable inside the closure, and such variables are captured by reference in Go.
Are they always, though? Surprisingly, the answer is no. In some cases, free variables are captured by value. Here's a variation of our example:
for i := 0; i < 5; i++ {
        ii := i
        go func() {
                foobyval(ii)
        }()
}
This will print 0, 1, 2, 3, 4 in a scrambled order. Why is the behavior here different from example 5? After all, ii is a free variable in the closure, same as i in example 5.
It turns out this behavior is an artifact of the heuristics used by the Go compiler to handle closures.
https://jnnctechnologies.com/
https://jnnctechnologies.com/category/blog/
https://twitter.com/jnnctechnologie
https://www.linkedin.com/company/14747569/admin/
https://www.facebook.com/jnnctechnologies.software/?ref=bookmarks
https://www.youtube.com/channel/UCDul6CfM-kNUhfTO597SCWQ

Post a Comment

0 Comments

'; (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })();