type Hello func() int
func sliceCounter1() []Hello {
hello := make([]Hello, 0, 8)
for i := 0; i < 5; i++ {
hello = append(hello, func() int {
fmt.Println(i)
return i
})
}
return hello
}
func sliceCounter2() []Hello {
hello := make([]Hello, 0, 8)
i := 0
for i < 5 {
hello = append(hello, func() int {
fmt.Println(i)
return i
})
i++
}
return hello
}
func main() {
sc1 := sliceCounter1()
sc2 := sliceCounter2()
for _, f1 := range sc1 {
f1()
}
fmt.Println("===========")
for _, f2 := range sc2 {
f2()
}
}
why the result is
0
1
2
3
4
===========
5
5
5
5
5
Why when I debug
the code, f1()
outputs 5 5 5 5 5
, but when I go run
the code, f1()
outputs 0 1 2 3 4
?
I can understand that f2()
refers to the address of i
, so when it is executed, it outputs the current value of i
. But why are the results of normal operation and debugging of f1()
inconsistent?
As @Volker mentioned, in sliceCounter1
you'll get a fresh variable for every loop execution since Go 1.22, so all the captured i
s are different.
This fixed a number a common bugs where people tried to capture the loop counter at it's current value, so the previous behavior - although correct - was pretty surprising. See the article for a longer explanation.
In sliceCounter2
you declare one single i
and capture that five times. At the time you decide to print it, its value is five.
For example
func main() {
i := 1
printI := func() {
fmt.Println(i)
}
incI := func() {
i++
}
incI()
incI()
printI()
incI()
printI()
}
Prints
3
4
as expected.
Edit I've mentioned the changed behavior since Go 1.22, but as @iBug correctly remarks in Go 1.21 or earlier, you get only one variable in sliceCounter1
and it prints all fives, like sliceCounter2
does. Usually, people don't expect this and the Go team considered this as an empirically non-breaking change.