Golang defer详解,以及常见的一些使用组合的理解。
定义
A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.
The behavior of defer statements is straightforward and predictable. There are three simple rules:
- A deferred function's arguments are evaluated when the defer statement is evaluated.
- Deferred function calls are executed in Last In First Out order after the surrounding function returns.
- Deferred functions may read and assign to the returning function's named return values.
- defer的函数参数在defer声明就被求值确定
- defer的函数调用顺序是先进后出
- defer的函数可以读取或者赋值具名返回值
函数参数
函数参数被立即求值的情况
package main
import "fmt"
func main() {
var i int = 1
defer fmt.Println("result =>",func() int { return i * 2 }())
i++
//prints: result => 2 (not ok if you expected 4)
}
func() int { return i * 2 }()
作为参数会被立即求值得到2,然后和函数fmt.Println
压入栈,因此输出是2
加上闭包
var whatever [5]struct{}
// part 1
for i := range whatever {
defer func() { fmt.Print(i) }()
}
// part 2
for i := range whatever {
defer func(n int) { fmt.Print(n) }(i)
}
$ 4321044444
由defer先进后出可知,part1输出其实是44444
,part2是输出是43210
。
part1循环里的函数是一个捕获了变量i
的闭包,变量i
在循环结束后值为4,这里因为闭包变量i
也被延长了生命周期。因此最后调用时,所有的defer函数都引用了同一个i
,所以结果是4.
part2就比较简单,由于i
是defer函数参数,被立即求值并保存,因此结果是43210
。
返回值
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", deferCall1()) // return:0
fmt.Println("return:", deferCall2()) // return:1
}
func deferCall1() int {
var i int
defer func() {
i++
}()
return i*2
}
func deferCall2() (i int) {
defer func() {
i++
}()
return i*2
}
由于deferCall1是匿名返回值,defer函数无法对返回值赋值,因此结果就是i*2 = 0
deferCall2的执行顺序是这样的:
- 执行
return i*2
语句,相当于i = i*2
此时返回值i
= = 0 - 执行defer里的
i++
,现在i
==1 - 最后返回
i
,即1
defer函数和具名返回值的精确的执行顺序是这样的:
- return 求值完成,赋值给具名返回值变量
- defer函数执行,defer可以读取修改具名返回值
- 函数返回具名返回值
如果defer的函数是变量?
handlers := []func(){
func() {
fmt.Print("A")
},
func() {
fmt.Print("B")
},
func() {
fmt.Print("C")
},
}
for _, h := range handlers {
defer h()
}
$ CBA
函数本身也是会被立即求值,因为要保存到栈中。
引用
50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs