I need to assign a variable to itself when iterating for the closure context to keep the correct value

huangapple go评论75阅读模式
英文:

I need to assign a variable to itself when iterating for the closure context to keep the correct value

问题

在不使用 i := i 的情况下,我得到了一个错误的结果 (3, 3, 5, 9, 7, 15)。而使用 i := i 后,我得到了正确的结果 (0, 0, 3, 3, 6, 10)。移除这个赋值语句类似于在循环结束时获取 i 的值。为什么会这样呢?

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 // *** 为什么要这样? ***
		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("\n")
}

这段代码中的 i := i 是一个很有趣的现象。在每次循环迭代时,都会创建一个新的 i 变量,并将其初始化为当前迭代的值。这样做的目的是为了在闭包函数中捕获正确的 i 值,而不是在循环结束后捕获最后一次迭代的值。如果不使用 i := i,闭包函数中的 i 将会捕获循环结束时的 i 值,导致输出结果不正确。通过使用 i := i,我们可以确保闭包函数中的 i 值与当前迭代的值保持一致,从而得到正确的结果。

英文:

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 &quot;fmt&quot;

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 := &amp;Elem{Handler: handlerFunc(fn)}
		elems = append(elems, elem)
	}

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

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

答案1

得分: 6

最简单的可视化方法是将函数的输出更改为:

newElem(func(v int) { fmt.Printf("加 %d - %d+%d\n", i+v, i, v) })
newElem(func(v int) { fmt.Printf("乘 %d - %d*%d\n", i*v, i , v) })

通过这个更改,输出变为:

加 3 - 3+0
乘 3 - 3*1
加 5 - 3+2
乘 9 - 3*3
加 7 - 3+4
乘 15 - 3*5

所以,你可以看到,在所有情况下,i 的值都是 3。这是因为你在 i 变量周围创建了闭包,所以函数在运行时会使用 i 的当前值,而函数实际使用 i 时,i 的值已经是 3。

如果你将代码更改如下,你还可以再次看到这一点:

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

在循环内部的赋值 i := i 修复了问题的原因是,你在循环的作用域内创建了一个新的变量 i,它仍然被函数所闭包,但它的值永远不会改变。每次循环的新迭代都会创建一个新的 i,因此之前的 i 不会改变值。

虽然这篇文档描述了在处理 goroutine 和闭包时的常见错误,但它应该对问题和潜在解决方案有所启示。

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

英文:

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

newElem(func(v int) { fmt.Printf(&quot;plus %d - %d+%d\n&quot;, i+v, i, v) })
newElem(func(v int) { fmt.Printf(&quot;times %d - %d*%d\n&quot;, 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

huangapple
  • 本文由 发表于 2016年2月29日 13:44:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/35692726.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定