gotchagoMajor
Loop variable capture in goroutines (pre-Go 1.22)
Viewed 0 times
Go 1.22 changed loop variable scoping
loop variable capturegoroutine closurerange loopvariable sharedgo 1.22closure capture
Problem
In Go versions before 1.22, goroutines launched inside a range loop that close over the loop variable all see the same final value of that variable by the time they execute.
Solution
Capture the loop variable explicitly by passing it as a function argument or shadowing it:
Go 1.22+ fixes this: each iteration creates a new loop variable automatically.
// WRONG (pre-1.22) — all goroutines print the last value of v
for _, v := range items {
go func() {
fmt.Println(v) // captures v by reference
}()
}
// RIGHT — shadow v inside the loop
for _, v := range items {
v := v // new variable scoped to this iteration
go func() {
fmt.Println(v)
}()
}
// ALSO RIGHT — pass as argument
for _, v := range items {
go func(val string) {
fmt.Println(val)
}(v)
}Go 1.22+ fixes this: each iteration creates a new loop variable automatically.
Why
Before Go 1.22, a single loop variable is reused for every iteration. Closures capture the variable address, so all goroutines share the same memory location which holds the final value after the loop ends.
Gotchas
- Go 1.22 changed the semantics — loop variables are now per-iteration. Code relying on the old behavior will break
- This affects for-range over slices, maps, channels, and integer ranges
- The race detector will not always catch this because it is a logic bug, not a data race
Revisions (0)
No revisions yet.