Loop variable not captured by goroutines

Why does this fail (always prints 3)

func main() {

	for i := 0; i < 3; i++ {
		go func() {
			fmt.Println(i)
		}()
	}

	time.Sleep(1 * time.Second)
}

while this works?

func main() {

	for i := 0; i < 3; i++ {
		func() {
			go fmt.Println(i)
		}()
	}

	time.Sleep(1 * time.Second)
}

Because at the time the go routines are scheduled, i is 3.

If you want to use outer values in a goroutine, pass them when creating the goroutine:

func main() {

	for i := 0; i < 3; i++ {
		go func(i int) {
			fmt.Println(i)
		}(i)
	}

	time.Sleep(1 * time.Second)
}

More info
https://eli.thegreenplace.net/2019/go-internals-capturing-loop-variables-in-closures/

You can use go vet to avoid this issues

Or go lint (includes go vet)

Go 1.21 will include an experimental setting to change the loop variable behavior. It solves this problem as well as similar loop problems outside goroutine context that include, in one way or another, delayed evaluation of the captured loop variable.

The setting is experimental (that is, it must be enabled manually), because the Go team is extremely cautious about language changes that might break existing Go code.

The Go team is extremely cautious about language changes that could damage existing Go code, hence the setting is experimental (i.e., it must be activated manually).

The Go team is extremely cautious about language changes that could damage existing Go code, hence the setting is experimental (i.e., it must be activated manually).

this is expected behavior, every other language with threading that can use a closure type function works this exact same way.

you either need to capture the state inside the closure. the best and least surprising way is with an argument to the function. the other way is subtle and can be missed by even people experienced with the language by just assigning a variable in the local scope to the value at the time the instance of the function is created, most of the time you see this done in the worst way possible; variable shadowing, which in your case would look like. “clever” programmers use the “shorthand” implicit version that catches newbies and even themselves out later when they are reading the code.

func() {
    i := i
    go fmt.Println(i)
}()

an explicit function argument is the best way to convey the semantic that is required.

func(i int) {
	go fmt.Println(i)
}(i)

This might be true, but then, it is expected behavior only to those who know how other languages with threading work.

Many newcomers stumble upon that behavior and would regard the aforementioned loopvar experiment as more logical than the current behavior.

To all the answers given, I think we have made clear why the first case does not work.

What I am missing (but perhaps has been somehow answered) is why the second case, i.e

func main() {

	for i := 0; i < 3; i++ {
		func() {
			go fmt.Println(i)
		}()
	}

	time.Sleep(1 * time.Second)
}

does work.

Here we don’t use a local scope variable nor do we use an argument to the anonymous function used by the loop.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.