Golang closures, counter-intuitive behaviour

162 views
Skip to first unread message

Slawomir Pryczek

unread,
Feb 11, 2019, 7:48:31 PM2/11/19
to golang-nuts
Hi Guys,

When looking at this code below, you can see that the function will get LAST value of i which is incremented inside the loop, but t is somehow copied and taken inside a function so closure is created and it is bound to current value of t, so we'll have its current value in function. This is strange as t and i is defined on the same code block level (t will be discarded at the same time as i, after the loop).

I always thought that t would need to be passed explicitly to goroutine to retain current value (which i did). But why assigning value in a loop is treated differently in this regard than a straight assignment? Any URL where such "special cases" are documented? Isn't it kind of design flaw, because this seem very confusing that loop is creating single value and simple assignment is creating multiple copies? Maybe you consider changing this in go2?


10 1
10 0
10 9
10 8
10 7
10 6
10 5
10 4
10 3
10 2

Thanks,
Slawomir.


-----
package main
import (
"fmt"
"time"
)

func main() {
for i := 0; i < 10; i++ {
t := i
go func() {
time.Sleep(time.Millisecond * 100)
fmt.Println(i, t)
}()
}
time.Sleep(time.Millisecond * 1000)
}



Ian Lance Taylor

unread,
Feb 11, 2019, 7:55:19 PM2/11/19
to Slawomir Pryczek, golang-nuts
On Mon, Feb 11, 2019 at 4:48 PM Slawomir Pryczek <slawe...@gmail.com> wrote:
>
> When looking at this code below, you can see that the function will get LAST value of i which is incremented inside the loop, but t is somehow copied and taken inside a function so closure is created and it is bound to current value of t, so we'll have its current value in function. This is strange as t and i is defined on the same code block level (t will be discarded at the same time as i, after the loop).
>
> I always thought that t would need to be passed explicitly to goroutine to retain current value (which i did). But why assigning value in a loop is treated differently in this regard than a straight assignment? Any URL where such "special cases" are documented? Isn't it kind of design flaw, because this seem very confusing that loop is creating single value and simple assignment is creating multiple copies? Maybe you consider changing this in go2?

A nested function that refers to a variable outside the closure is
always using a reference to that variable, never a copy. So the only
question here is about the handling of variables in a loop. The rule
is simply that a new variable is created at each variable declaration.
In the loop, there is one variable declaration of i when the loop
starts: i := 0. The other references to i, including all the uses of
i within the loop, all refer to that single variable. But the
declaration "t := i" is declared each time through the loop, so each
separate iteration of the loop has a different variable t.

So there is no special case here, and this is working as intended.
Hope this helps.

Ian

Burak Serdar

unread,
Feb 11, 2019, 7:58:52 PM2/11/19
to Slawomir Pryczek, golang-nuts
On Mon, Feb 11, 2019 at 5:48 PM Slawomir Pryczek <slawe...@gmail.com> wrote:
>
> Hi Guys,
>
> When looking at this code below, you can see that the function will get LAST value of i which is incremented inside the loop, but t is somehow copied and taken inside a function so closure is created and it is bound to current value of t, so we'll have its current value in function. This is strange as t and i is defined on the same code block level (t will be discarded at the same time as i, after the loop).
>
> I always thought that t would need to be passed explicitly to goroutine to retain current value (which i did). But why assigning value in a loop is treated differently in this regard than a straight assignment? Any URL where such "special cases" are documented? Isn't it kind of design flaw, because this seem very confusing that loop is creating single value and simple assignment is creating multiple copies? Maybe you consider changing this in go2?


This is well-documented behavior. The first link I can find is:
https://github.com/golang/go/wiki/CommonMistakes

The common mistake is to think that the for-loop variable is captured
as value in the closure. It is not, it is a reference, and the
go-routine sees the value of the variable when it runs, not when it is
created.
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Tyler Compton

unread,
Feb 11, 2019, 8:15:50 PM2/11/19
to Burak Serdar, Slawomir Pryczek, golang-nuts
For what it's worth, there is a popular proposal up that suggests changing for range loops to redefine their variables for every iteration: https://golang.org/issues/20733
Reply all
Reply to author
Forward
0 new messages