为什么gopanic恢复到带有局部变量的返回值不起作用?
此紧急恢复代码适用于命名的返回值。
func main() {
result, err := foo()
fmt.Println("result:", result)
if err != nil {
fmt.Println("err:", err)
}
}
func foo() (result int, err error) {
defer func() {
if e := recover(); e != nil {
result = -1
err = errors.New(e.(string))
}
}()
bar()
result = 100
err = nil
return
}
func bar() {
panic("panic happened")
}
输出
result: -1
err: panic happened
但是为什么这段带有局部变量的代码不起作用?
func main() {
result, err := foo()
fmt.Println("result:", result)
if err != nil {
fmt.Println("err:", err)
}
}
func foo() (int, error) {
var result int
var err error
defer func() {
if e := recover(); e != nil {
result = -1
err = errors.New(e.(string))
}
}()
bar()
result = 100
err = nil
return result, err
}
func bar() {
panic("panic happened")
}
输出
result: 0
有什么解释可以帮助我理解它的原因/基本概念吗?在 go tour basics 中,解释如下。
命名返回值 Go 的返回值可以命名。如果是这样,它们将被视为在函数顶部定义的变量。
所以应该是一样的吧?
回答
规范:返回语句详细说明:
有三种方法可以从具有结果类型的函数返回值:
- 返回值可以在“return”语句中明确列出。每个表达式必须是单值的并且可以分配给函数结果类型的相应元素。
- “return”语句中的表达式列表可能是对多值函数的单个调用。效果就好像从该函数返回的每个值都被分配给具有相应值类型的临时变量,然后是列出这些变量的“返回”语句,此时适用前一种情况的规则。
- 如果函数的结果类型为其结果参数指定名称,则表达式列表可能为空。结果参数作为普通的局部变量,函数可以根据需要为它们赋值。“return”语句返回这些变量的值。
因此,基本上如果您使用return显式列出返回值的语句,则无论结果参数是否已命名,都将使用这些值。
如果结果参数被命名,则它们充当普通的局部变量:您可以读取和写入它们。如果结果参数被命名,您可以使用“裸”return语句,而不列出要返回的值。如果这样做,则实际返回值将是(命名的)结果参数的值。如果您的函数return由于恐慌和恢复而未到达语句,则同样适用:一旦延迟函数运行,实际返回值将是命名结果参数的值(延迟函数可以更改并“有发言权”)在返回什么)。
如果您不使用命名结果参数但声明了局部变量,则它们在这种方式中并不特殊:当函数返回时,它们不会“自动”用作结果值(就像它们被命名为 result 时那样)参数而不是局部变量)。因此,如果您在延迟函数中更改它们,则不会对返回的实际值产生任何影响。实际上,如果您不使用命名结果参数并且您的函数发生恐慌和恢复,则无法指定返回值,它们将是结果类型的零值。这就是为什么您看到result: 0(0是 的零值int) 并且没有错误(因为error是接口类型,而接口类型的零值是nil如果是nil),则不会打印错误。
请参阅相关:如何在发生恐慌的 Go 函数中返回一个值?