我需要在迭代时为闭包上下文分配一个变量,以保持正确的值

Without i := i, I get an incorrect result (3, 3, 5, 9, 7, 15). With it, I get (0, 0, 3, 3, 6, 10), which is correct. Removing the assignment is similar to getting the value of i at the end of the loop. Why?

package main

import "fmt"

type Handler interface {
    Handle(v int)
}

type Elem struct {
    Handler Handler
}

var elems []*Elem

type handlerFunc func(v int)

func (h handlerFunc) Handle(v int) { h(v) }

func main() {
    newElem := func(fn handlerFunc) {
        elem := &Elem{Handler: handlerFunc(fn)}
        elems = append(elems, elem)
    }

    for i := 0; i < 3; i++ {
        i := i // *** Why? ***
        newElem(func(v int) { fmt.Printf("%d, ", i+v) })
        newElem(func(v int) { fmt.Printf("%d, ", i*v) })
    }

    for n, e := range elems {
        if e.Handler != nil {
            e.Handler.Handle(n)
        }
    }
    fmt.Printf("
")
}

The easiest way to visualize what is going on is to change the output of your functions to:

newElem(func(v int) { fmt.Printf("plus %d - %d+%d
", i+v, i, v) })
newElem(func(v int) { fmt.Printf("times %d - %d*%d
", i*v, i , v) })

With this change the output becomes:

plus 3 - 3+0
times 3 - 3*1
plus 5 - 3+2
times 9 - 3*3
plus 7 - 3+4
times 15 - 3*5

So, as you can see, i is 3 in all cases. This is because you are creating closures around the i variable, so the functions will use the current value of i when they run, which is 3 by the time the functions actually use i.

You can again see this if you change your code like the following:

http://play.golang.org/p/FRhr0n2oi7

The reason the assignment i := i inside your loop fixes the problem is because you are creating a new variable i inside the scope of your loop which is still closed over by the functions, but doesn't ever change. Each new iteration of the loop creates a new i so no previous i changes value.

Although this document describes a common mistake when dealing with goroutines and closures, it should shed a bit more light on the issue and potential solutions.

https://github.com/golang/go/wiki/CommonMistakes