Go 语言里 defer, panic 和 named return values
Go 语言里,函数过程中通过 defer 注册的回调,会在函数结束前、按 LIFO 的顺序调用执行。有名返回值(named return value)在编程时,恰当的变量名,结合类型,对于走读代码、或者生成的文档,会更加清晰和直观。
当两者混用的时候,要注意相关步骤调用的先后顺序对实际返回值的影响。
先说结论:
- 如果是普通的返回值:
- 函数结束时 return 的值,会被放到
return value
里;如果因为 panic() 异常而没有走到 return 的代码,return value
应该是返回值类型对应的默认值; - defer 回调里对变量的操作,不影响
return value
;
- 函数结束时 return 的值,会被放到
- 如果是 named return value,所有作用在返回值变量上的操作,等同于
return value
; - caller 拿到
return value
里的返回值;
1. 比如:
func test() int {
ret := 1
defer func() {
ret += 3
}()
defer func() {
ret *= 2
}()
fmt.Sprintf("test .. ret=%d", ret)
return ret
}
return ret
,把当前值 1 放到return value
里;- 运行两个 defer 函数,先运行
ret *= 2
得到 2,再运行ret += 3
得到 5;但 ret 只是临时变量,不影响return value
; - 把
return value
里的值交给 caller。
所以,这里返回的是 1。
2. 作为对比:
func test() (ret int) {
ret = 1
defer func() {
ret += 3
}()
defer func() {
ret *= 2
}()
fmt.Sprintf("test .. ret=%d", ret)
return ret
}
现在 ret 是 named return value
:
return ret
把当前值 1 放到return value
(即 ret)里;- 运行两个 defer 函数,先运行
ret *= 2
得到 2,再运行ret += 3
得到 5。这里对 ret 的修改,等同于影响return value
; - 把
return value
(即 ret)里的值交给 caller。
所以,这里返回的是 5。
3. 添加 panic
func test() (ret int) {
ret = 1
defer func() {
if r := recover(); r != nil {
ret *= 20
}
}()
defer func() {
if r := recover(); r != nil {
ret *= 10
}
}()
defer func() {
ret += 3
}()
defer func() {
ret *= 2
}()
panic("some error")
fmt.Sprintf("test .. ret=%d", ret)
return ret
}
在前一个例子的基础上,添加了 panic
和 recover
的处理。
panic("some error")
后面的代码执行不到了,不过这时return value
里已经是 1;- 运行几个 defer 函数,先运行
ret *= 2
得到 2,运行ret += 3
得到 5,再运行 recover() 一段里的ret *= 10
得到 50,最后的 recover() 一段,因为这时 r == nil,就不执行了。这里对 ret 的修改,等同于影响return value
; - 把
return value
(即 ret)里的值交给 caller。
所以,这里返回的是 50。
另外,如果这里的 ret
不是 named return value
,而是临时变量,那么即使变量在初始时赋值为 1,因为 panic("some error")
后面的代码执行不到,所以这时 return value
里仍然是默认值 0;并且几个 defer 函数里的修改作用在临时变量上,并不涉及到 return value
,所以最后返回的是 0。