Consider the following Go code (also on the Go Playground):
package main
import "fmt"
import "time"
func main() {
for _, s := range []string{"foo", "bar"} {
x := s
func() {
fmt.Printf("s: %s\n", s)
fmt.Printf("x: %s\n", x)
}()
}
fmt.Println()
for _, s := range []string{"foo", "bar"} {
x := s
go func() {
fmt.Printf("s: %s\n", s)
fmt.Printf("x: %s\n", x)
}()
}
time.Sleep(time.Second)
}
This code produces the following output:
s: foo
x: foo
s: bar
x: bar
s: bar
x: foo
s: bar
x: bar
Assuming this isn't some odd compiler bug, I'm curious why a) the value of s is interpreted differently in the goroutine version then in the regular func call and b) and why assigning it to a local variable inside the loop works in both cases.
Closures in Go are lexically scoped. This means that any variables referenced within the closure from the "outer" scope are not a copy but are in fact a reference. A for
loop actually reuses the same variable multiple times, so you're introducing a race condition between the read/write of the s
variable.
But x
is allocating a new variable (with the :=
) and copying s
, which results in that being the correct result every time.
In general, it is a best practice to pass in any arguments you want so that you don't have references. Example:
for _, s := range []string{"foo", "bar"} {
x := s
go func(s string) {
fmt.Printf("s: %s\n", s)
fmt.Printf("x: %s\n", x)
}(s)
}