Go闭包捕获规则与延迟有什么不同?

时间:2016-09-16 17:57:08

标签: go

以下Go代码演示了defer和go闭包之间闭包捕获规则的区别。在一个教程中,我被告知for循环变量的范围仅限于循环体,但这里看起来有些不同。

package main

import "fmt"

func deferred() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
}

func cps() {
    clo := new(func())
    *clo = func() {}
    defer func() { (*clo)() }()
    for i := 0; i < 5; i++ {
        oldClo := *clo
        *clo = func() {
            fmt.Println(i)
            oldClo()
        }
    }
}

func cpsCpy() {
    clo := new(func())
    *clo = func() {}
    defer func() { (*clo)() }()
    for i := 0; i < 5; i++ {
        oldClo := *clo
        cpy := i
        *clo = func() {
            fmt.Println(cpy)
            oldClo()
        }
    }
}

func main() {
    fmt.Println("defer")
    deferred()
    fmt.Println("cps")
    cps()
    fmt.Println("cpsCpy")
    cpsCpy()
}

这会产生输出:

defer
4
3
2
1
0
cps
5
5
5
5
5
cpsCpy
4
3
2
1
0

如果差异是故意的,那么有什么不同的用例可以证明它的合理性呢?

1 个答案:

答案 0 :(得分:2)

spec for defer对此很清楚。对于那些关心变量捕获规则之类的东西而言,这个规范通常是至关重要的读物,并且相对较短。它在这里说的是:

  

每次执行“延迟”语句时,将像往常一样评估调用的函数值和参数,并重新保存但不调用实际函数。

the blog post on defer, panic, and recover也涵盖了这一点。

总是很难证明设计决策的合理性让每个人都感到满意,而defer尤其让人偶尔感到惊讶,特别是当你defer f1(f2(x))忘记f2(x)时立即

可能有助于记住它的一件事是,defer(或go)之后的内容是一个函数调用,而只是函数调用本身(甚至连params的评估都没有及时改变。闭包定义了代码块,并在代码执行时访问变量。

由于您对使用闭包获得的变量捕获类型感到好奇,因此如果您使用类似ForEach的方法的闭包,那么它就是一个有用的示例:

myBTree.ForEach(func (key, value []byte) {
    //...
})

如果在常规for循环中,大括号之间的代码可以修改外部作用域中的变量,那就太好了。这要求闭包访问变量,而不仅仅是它们在特定时间内的值。