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。