defer
是Go
语言中的关键字,也是Go
语言的重要特性之一。
defer
语法形式如下:
defer Expression
其后必须跟一个函数调用或者方法调用,不能用括号括起来。在Go
语言中,defer
一般被用于资源的释放及异常panic
的处理。
本文主要介绍defer
非常容易犯错的场景:参数预计算和返回值陷阱。
defer
的一个特性是参数的预计算,这一特性时常导致开发者在使用defer
时犯错。因为在大部分时候,我们记住的都是其延迟执行的特性。参数的预计算指当函数到达defer
语句时,延迟调用的参数将立即求值,传递到defer
函数中的参数将预先被固定,而不会等到函数执行完成后再传递参数到defer
中。
如下例所示:
func Example1() {
a := 1
defer func(b int) {
fmt.Printf("defer b: %d", b)
}(a + 1)
a = 100
// Output: defer b: 2
}
defer
函数最终输出的b值为2。原因是传递到defer
的参数是预执行的,因此在执行到defer
语句时,执行了a+1
并将其保留起来了,直到函数执行完成后才执行defer
函数体内的语句。
如下所示:
var a = 1
func f() (b int) {
defer func() {
a = 2
}()
fmt.Printf("f: a=%d, ", a)
return a
}
func Example2() {
i := f()
fmt.Printf("main: i=%d, a=%d", i, a)
// Output: f: a=1, main: i=1, a=2
}
从程序输出结果可以推测出:在return
之后,执行了defer
函数。
将上述代码稍作修改,如下所示:
var a = 1
func f() (b int) {
b = a
defer func() {
b = 2
}()
b = 0
return b
}
func Example() {
i := f()
fmt.Printf("main: i=%d, a=%d", i, a)
// Output: main: i=2, a=1
}
从这个结果中,我们又可以推测出defer执行完成后,执行了return
语句。因为其返回值i
是在defer
中赋值的。
两种代码的输出结果推出的结论截然相反,原因在于return
其实并不是一个原子操作,其包含了下面几步:
所以第一个示例中的函数f
可以翻译为如下伪代码,最终返回值b
为1:
a = 1
b = a
a = 2
return
第二个示例中的函数f可以翻译为如下伪代码,最终返回值b
为2
a = 1
b = a
b = 0
b = 2
return
defer
是Go
语言重要的特性之一,defer
一般用于资源释放、异常捕获、中间件等场景。当有多个defer
函数时,按照后入先出last-in-first-out, LIFO
的顺序执行。另外,特别要注意与返回值结合时的陷阱,这是由于return
语句并不是一条原子操作。