Go Shorts #1

June 27, 2022

In this series, I will cover interesting aspects of Go in a relatively brief manner.

Can you figure out what is wrong with the following program, which tries to print the keys and values of a map concurrently?

m := map[int]int{
    0: 0,
    1: 1,
    2: 2,
    3: 3,
}

for k, v := range m {
    go func() {
        fmt.Println(k, v)
    }()
}

The behavior of the above program is undefined, as it will likely lead to data races. In Go, loop variables (k and v in this case) are reused in each iteration. The function literal, which is a closure, encloses them, causing all the created goroutines to see the "same" variables: the loop variables k and v. To fix this, copy the loop variables, like so:

for k, v := range m {
    k, v := k, v // This is idiomatic Go.
    go func() {
        fmt.Println(k, v)
    }()
}

Note that this only appertains to closures. If you use go fmt.Println(k, v) instead, you do not have to worry—k and v are evaluated before the launch of the goroutine(s), and a copy is passed to the function.

Tip: Use the -race flag with go run or go build to detect data races in your program:

$ go run -race reuse_loop_var.go
0 0
0 0
==================
WARNING: DATA RACE
Read at 0x00c000138018 by goroutine 7:
  main.main.func1()
      /home/ab/src/playground/reuse_loop_var.go:19 +0x52

Previous write at 0x00c000138018 by main goroutine:
  main.main()
      /home/ab/src/playground/reuse_loop_var.go:16 +0x28e

Goroutine 7 (running) created at:
  main.main()
      /home/ab/src/playground/reuse_loop_var.go:18 +0x215
==================
==================
WARNING: DATA RACE0 0

Read at 0x00c000138028 by goroutine 8:
  main.main.func1()
      /home/ab/src/playground/reuse_loop_var.go:19 +0x84

Previous write at 0x00c000138028 by main goroutine:
  main.main()
      /home/ab/src/playground/reuse_loop_var.go:16 +0x2ab

Goroutine 8 (running) created at:
  main.main()
      /home/ab/src/playground/reuse_loop_var.go:18 +0x215
==================
3 0
Found 2 data race(s)
exit status 66