Loop Var Capture
ID |
go.loop_var_capture |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Go |
Tags |
concurrency, reliability |
Description
Reports a for/range loop variable captured by reference inside a closure that is launched
with go or scheduled with defer in the same loop.
Rationale
Before Go 1.22 the loop variable was a single binding reused across iterations, so every closure captures the same variable and observes its final value instead of the value at launch time — a classic concurrency bug that fails intermittently. Passing the value as an argument, or shadowing it into a fresh per-iteration variable, makes each closure see the intended value. Go 1.22 changed the semantics so each iteration gets a fresh variable, but the pattern remains a defect for modules targeting earlier language versions and is a portability/readability smell either way.
// Bad
for _, v := range xs {
go func() { use(v) }() // FLAW — all goroutines share the one v
}
// Good
for _, v := range xs {
go func(s T) { use(s) }(v) // OK — value passed as an argument
}
for _, v := range xs {
v := v // OK — fresh per-iteration variable
go func() { use(v) }()
}
Remediation
Pass the loop variable to the closure as an argument, or shadow it with v := v at the top of
the loop body so each iteration captures its own copy.