无法理解5.6.1。警告:捕获迭代变量

时间:2018-10-25 01:45:05

标签: go

我正在学习The go programming language并且听不懂

 var rmdirs []func()

 for _, dir := range tempDirs() {
     os.MkdirAll(dir, 0755)
     rmdirs = append(rmdirs, func() {
         os.RemoveAll(dir) // NOTE: incorrect!
     })
 }

我已经阅读了本书中的解释多次,仍然无法弄清为什么不正确?

我记得在go参数中传递的是值,所以每个循环dir都是不同的值,为什么不正确?

1 个答案:

答案 0 :(得分:3)

您的直觉是正确的:go reuses the same address for the iteration values,因此无法保证当附加到dir的匿名函数时,rmdirs指向的值与函数相同被创建,dir首先被捕获。 The exact wording in the specs是:

  

可以通过“范围”子句使用短变量声明(:=)的形式声明迭代变量。在这种情况下,将它们的类型设置为各个迭代值的类型,并且它们的范围是“ for”语句的块; 它们在每次迭代中都会重复使用。如果迭代变量是在“ for”语句之外声明的,则执行后,它们的值将是上一次迭代的值。

(强调我的)。为了进一步说明,这是您的代码试图做的简化版本:

var rmdirs []func()
tempDirs := []string{"one", "two", "three", "four"}

for _, dir := range tempDirs {
    fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    rmdirs = append(rmdirs, func() {
        fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    })
}

fmt.Println("---")

for _, f := range rmdirs {
    f()
}

运行时,将产生以下输出:

dir=one, *dir=0x40e128
dir=two, *dir=0x40e128
dir=three, *dir=0x40e128
dir=four, *dir=0x40e128
---
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128

游乐场链接:https://play.golang.org/p/_rS8Eq9qShM

如您所见,在整个循环中,每次迭代都会重复使用相同的地址。匿名函数的每次迭代都将查找相同的地址,因此它们都将打印相同的值。

如您所参考的书所述,处理此类情况的正确方法是在循环中定义一个新变量,将迭代值复制到该变量中,然后将其传递给匿名函数,如下所示:

var rmdirs []func()
tempDirs := []string{"one", "two", "three", "four"}

for _, d := range tempDirs {
    dir := d
    fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    rmdirs = append(rmdirs, func() {
        fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    })
}

fmt.Println("---")

for _, f := range rmdirs {
    f()
}

这将产生您期望的输出:

dir=one, *dir=0x40e128
dir=two, *dir=0x40e150
dir=three, *dir=0x40e168
dir=four, *dir=0x40e180
---
dir=one, *dir=0x40e128
dir=two, *dir=0x40e150
dir=three, *dir=0x40e168
dir=four, *dir=0x40e180

游乐场链接:https://play.golang.org/p/Ao6fC9i2DsG