With the release of Go 1.22, the Go team addressed a long-standing issue with the for-range
loop variable scoping, which had been a common pitfall for developers writing concurrent code. Specifically, the problem was that the loop variable in a for-range
loop was reused across iterations, leading to unexpected behavior when closures were involved.
The Issue Before Go 1.22
Consider the following example:
package forange
import "fmt"
func Do() {
done := make(chan bool)
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v) // Prints unexpected values
done <- true
}()
}
for _ = range values {
<-done
}
}
And there is a go.mod file as below
module playground
go 1.16
require github.com/ozgio/strutil v0.4.0
In versions of Go prior to 1.22, running this code often resulted in printing the same value (e.g., c
) multiple times instead of printing each value in values
(a
, b
, c
) as expected. This was because the loop variable v
was reused across iterations, and the goroutines captured the same memory address.
The Go 1.22 Fix
Go 1.22 introduced a fix to this behavior by making the loop variable behave as if it were declared fresh in each iteration. This means each closure should now correctly capture the value of v
for its corresponding iteration.
However, if you’ve upgraded to Go 1.22 and still observe the old, problematic behavior, the culprit might be your go.mod
file. The fix only takes effect if the go.mod
file specifies a Go version of 1.22
or higher. This dependency is intentional and allows developers to retain backward compatibility with older behaviors if needed.
With go 1.22
specified in your go.mod
and the fix applied, running the above program should correctly print a, b, c. Each goroutine now captures the proper value of v
for its iteration.
Lessons Learned
- Stay Updated: Always read the release notes for new Go versions. The Go 1.22 release notes specifically mention this change and its dependency on
go.mod
. - Use Correct
go.mod
Versioning: Many Go language features are tied to thego
version ingo.mod
. If you’re relying on new features or fixes, ensure the correct version is specified. - Test Thoroughly: Even after upgrading Go, ensure that your programs behave as expected by running comprehensive tests.
Why Is This Dependency Necessary?
The go.mod
versioning approach allows teams to adopt fixes incrementally without breaking existing builds. By coupling new language behavior to the go
version in go.mod
, the Go team ensures backward compatibility while enabling smoother migrations for developers.
If you’ve experienced similar challenges or have insights on how this has impacted your workflow, feel free to share in the comments!