What does this print? (Hint: consider the go line in go.mod.)
// go.mod: go 1.21
package main
import "fmt"
func main() {
var fns []func()
for i := 0; i < 3; i++ {
fns = append(fns, func() { fmt.Print(i, " ") })
}
for _, f := range fns {
f()
}
}
Answer: with go 1.21 (or lower) it prints 3 3 3. Change go.mod to go 1.22 and it prints 0 1 2, even when you compile both with the same Go 1.23 toolchain.
The per-iteration loop variable semantics of Go 1.22 are gated on the language version of the module (or file) being compiled, not on the toolchain version. This kept the change backward compatible: old dependencies keep their old behavior while your module gets the new one.
You can also override the version per file with a build constraint, which sets that file's language version:
//go:build go1.22
package main
Interviewers use this to test whether you know that the go line is more than documentation. Related tooling: go vet's loopclosure check, and bisect / -gcflags=-d=loopvar=2, which helped find code that relied on the old sharing behavior when the change shipped.
More on Modules, Packages & Tooling
- Q418How does the Go checksum database protect the supply chain? What does go mod verify actually check?
- Q419What do the go and toolchain directives mean since Go 1.21, and how does GOTOOLCHAIN behave?
- Q421What does the Go 1.24 tool directive replace, and how do you use it?
- Q422What is the difference between go get and go install pkg@version in module mode?
- Q423Explain build constraints: the //go:build syntax, filename rules, and common gotchas.
- Q424How would you separate integration tests from unit tests using build tags? What alternatives exist?