Go

What does this print before and after Go 1.22? (taking &v and capturing i in loops)

Question 72HardGo 1.22 to 1.25
var ptrs []*int
for _, v := range []int{1, 2, 3} {
	ptrs = append(ptrs, &v)
}
var funcs []func()
for i := 0; i < 3; i++ {
	funcs = append(funcs, func() { fmt.Print(i, " ") })
}

for _, p := range ptrs {
	fmt.Print(*p, " ")
}
fmt.Println()
for _, f := range funcs {
	f()
}

Output with Go 1.22+ semantics:

1 2 3
0 1 2

Output with Go 1.21 and earlier semantics:

3 3 3
3 3 3

Before 1.22, a for loop declared its variables once for the whole loop. Every &v and every closure pointed at the same variable, which held its final value after the loop. For the three-clause loop that is 3, the value that ended the loop. Since Go 1.22 each iteration gets a fresh copy of the variable. In a three-clause loop, the new copy starts with the value the previous iteration ended with, before the post statement runs.

Details interviewers probe:

  • The behaviour depends on the go line in go.mod (1.22 or later), not on the toolchain. An old module built with a new compiler keeps the old semantics. A file can also be pinned with a //go:build go1.21 constraint.
  • The compiler allocates a fresh variable per iteration only when it escapes (it is captured or has its address taken), so ordinary loops cost nothing extra.
  • Building with -gcflags=all=-d=loopvar=2 lists every loop whose semantics changed. To find which loop breaks a test, use golang.org/x/tools/cmd/bisect with -d=loopvarhash=PATTERN.
  • &v still points to a copy of the element, not into the slice. Use &s[i] to alias the element.

More on Arrays, Slices, Maps & Strings

All 37 Arrays, Slices, Maps & Strings questions