Captured closure (for loop variable) in Go

Shouldn't the Jump compiler capture the for...range

loop variables as a locally assigned closure variable?

Long version:

This caused me some confusion in C # and I was trying to understand it; why is it fixed in C # 5.0 foreach

(reason: the loop variable cannot change inside the loop body) and the reasoning for not committing it in the C # loop for

(reason: the loop variable can change inside the loop body).

Now (for me) for...range

loop in Go look as loops foreach

in C #, but, despite the fact that we can not change these variables (for example, k

and v

in a for k, v := range m { ... }

)); however, we must first copy them into some local locks so that they can behave as expected.

What is the reason for this? (I suspect because Go handles any loop for

in the same way, but I'm not sure).

Here are some examples to explore the described behavior:

func main() {
    lab1() // captured closure is not what is expected
    fmt.Println(" ")

    lab2() // captured closure is not what is expected
    fmt.Println(" ")

    lab3() // captured closure behaves ok
    fmt.Println(" ")
}

func lab3() {
    m := make(map[int32]int32)
    var i int32
    for i = 1; i <= 10; i++ {
        m[i] = i
    }

    l := [](func() (int32, int32)){}
    for k, v := range m {
        kLocal, vLocal := k, v // (C) captures just the right values assigned to k and v
        l = append(l, func() (int32, int32) {
            return kLocal, vLocal
        })
    }

    for _, x := range l {
        k, v := x()
        fmt.Println(k, v)
    }
}

func lab2() {
    m := make(map[int32]int32)
    var i int32
    for i = 1; i <= 10; i++ {
        m[i] = i
    }

    l := [](func() (int32, int32)){}
    for k, v := range m {
        l = append(l, func() (int32, int32) {
            kLocal, vLocal := k, v // (B) captures just the last values assigned to k and v from the range
            return kLocal, vLocal
        })
    }

    for _, x := range l {
        k, v := x()
        fmt.Println(k, v)
    }
}

func lab1() {
    m := make(map[int32]int32)
    var i int32
    for i = 1; i <= 10; i++ {
        m[i] = i
    }

    l := [](func() (int32, int32)){}
    for k, v := range m {
        l = append(l, func() (int32, int32) { return k, v }) // (A) captures just the last values assigned to k and v from the range
    }

    for _, x := range l {
        k, v := x()
        fmt.Println(k, v)
    }
}

      

As shown in the lab1

comment, // (A)

we only get the latest values ​​from range

; the conclusion is similar to print 9,9

ten times instead of displaying the expected result, such as 1,1

, 2,2

... (and course maps are not necessarily sorted in Go, so we can see 3,3

ten times since the last pair of values, rather than 10,10

ten times as the last pair of values). The same applies to the code in the comment // (B)

AT lab2

, which was expected, because we are trying to capture the external variables in the inner region (I also put it to try). The lab3

code in the comments // (C)

everything is working properly, and you'll see there for ten pairs of numbers, such as 1,1

, 2,2

, ....

I tried to use closure + function as a replacement for s tuple in Go.

+3


source to share


1 answer


Do you want to close a variable or value? For example,

package main

import "fmt"

func VariableLoop() {
    f := make([]func(), 3)
    for i := 0; i < 3; i++ {
        // closure over variable i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("VariableLoop")
    for _, f := range f {
        f()
    }
}

func ValueLoop() {
    f := make([]func(), 3)
    for i := 0; i < 3; i++ {
        i := i
        // closure over value of i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("ValueLoop")
    for _, f := range f {
        f()
    }
}

func VariableRange() {
    f := make([]func(), 3)
    for i := range f {
        // closure over variable i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("VariableRange")
    for _, f := range f {
        f()
    }
}

func ValueRange() {
    f := make([]func(), 3)
    for i := range f {
        i := i
        // closure over value of i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("ValueRange")
    for _, f := range f {
        f()
    }
}

func main() {
    VariableLoop()
    ValueLoop()
    VariableRange()
    ValueRange()
}

      

Output:



VariableLoop
3
3
3
ValueLoop
0
1
2
VariableRange
2
2
2
ValueRange
0
1
2

Literature:

Go programming language specification

Function literals

Function literals are closures: they can refer to variables defined in the surrounding function. These variables are then distributed between the surrounding function and the literal function, and they survive as long as they are available.

Go FAQ: What Happens to Closures Executed as goroutines?

To bind the current value of v to each closure as it runs, one has to modify the inner loop to create a new variable for each iteration. One way is to pass a variable as an argument to the closure.

It's even easier to just create a new variable using a style declaration that might sound strange but works great in Go.

+3


source







All Articles