为什么 `defer recover()` 不会引起恐慌?
Why does `defer recover()` not catch panics?
为什么调用 defer func() { recover() }()
可以成功恢复一个恐慌的 goroutine,但调用 defer recover()
却不能?
作为一个简单的例子,这段代码不会恐慌
package main
func main() {
defer func() { recover() }()
panic("panic")
}
但是,直接用recover代替匿名函数会出现panic
package main
func main() {
defer recover()
panic("panic")
}
Handling panic 部分提到
Two built-in functions, panic
and recover
, assist in reporting and handling run-time panics
The recover
function allows a program to manage behavior of a panicking goroutine.
Suppose a function G
defers a function D
that calls recover
and a panic
occurs in a function on the same goroutine in which G
is executing.
When the running of deferred functions reaches D
, the return value of D
's call to recover
will be the value passed to the call of panic.
If D returns normally, without starting a new panic, the panicking sequence stops.
这说明 recover
应该在延迟函数中调用,而不是直接调用。
panic时,"deferred function"不能是内置的recover()
,而是defer statement.
中指定的
DeferStmt = "defer" Expression .
The expression must be a function or method call; it cannot be parenthesized.
Calls of built-in functions are restricted as for expression statements.
With the exception of specific built-in functions, function and method calls and receive operations can appear in statement context.
引用自内置函数的文档recover()
:
If recover is called outside the deferred function it will not stop a panicking sequence.
在你的第二种情况下 recover()
本身就是延迟函数,显然 recover()
不会调用自己。所以这不会停止恐慌序列。
如果 recover()
本身会调用 recover()
,它会停止恐慌序列(但为什么要那样做?)。
另一个有趣的例子:
下面的代码也不死机(在Go Playground上试试):
package main
func main() {
var recover = func() { recover() }
defer recover()
panic("panic")
}
这里发生的是我们创建一个函数类型的 recover
变量,它的值是调用内置 recover()
函数的匿名函数。我们指定调用 recover
变量的值作为延迟函数,因此从中调用内置 recover()
停止恐慌序列。
据观察,这里真正的问题是 defer
的设计,因此答案应该是这样。
激发这个答案,defer
目前需要从 lambda 中取出一层嵌套堆栈,并且运行时使用此约束的特定副作用来确定是否 recover()
return是否为零。
这是一个例子:
func b() {
defer func() { if recover() != nil { fmt.Printf("bad") } }()
}
func a() {
defer func() {
b()
if recover() != nil {
fmt.Printf("good")
}
}()
panic("error")
}
b()
中的recover()
应该return为零。
在我看来,更好的选择是说 defer
将函数 BODY 或块作用域(而不是函数调用)作为其参数。那时,panic
和 recover()
return 值可以绑定到特定的堆栈帧,并且任何内部堆栈帧都会有一个 nil
平移上下文。因此,它看起来像这样:
func b() {
defer { if recover() != nil { fmt.Printf("bad") } }
}
func a() {
defer {
b()
if recover() != nil {
fmt.Printf("good")
}
}
panic("error")
}
在这一点上,很明显 a()
处于恐慌状态,但 b()
不是,并且 "being in the first stack frame of a deferred lambda" 之类的副作用对于正确实施运行时间。
所以,在这里违背常理:这不像预期的那样工作的原因是 go 语言中 defer
关键字的设计错误,该关键字是使用 non - 明显的实现细节副作用,然后这样编纂。
为什么调用 defer func() { recover() }()
可以成功恢复一个恐慌的 goroutine,但调用 defer recover()
却不能?
作为一个简单的例子,这段代码不会恐慌
package main
func main() {
defer func() { recover() }()
panic("panic")
}
但是,直接用recover代替匿名函数会出现panic
package main
func main() {
defer recover()
panic("panic")
}
Handling panic 部分提到
Two built-in functions,
panic
andrecover
, assist in reporting and handling run-time panicsThe
recover
function allows a program to manage behavior of a panicking goroutine.Suppose a function
G
defers a functionD
that callsrecover
and apanic
occurs in a function on the same goroutine in whichG
is executing.When the running of deferred functions reaches
D
, the return value ofD
's call torecover
will be the value passed to the call of panic.
If D returns normally, without starting a new panic, the panicking sequence stops.
这说明 recover
应该在延迟函数中调用,而不是直接调用。
panic时,"deferred function"不能是内置的recover()
,而是defer statement.
DeferStmt = "defer" Expression .
The expression must be a function or method call; it cannot be parenthesized.
Calls of built-in functions are restricted as for expression statements.With the exception of specific built-in functions, function and method calls and receive operations can appear in statement context.
引用自内置函数的文档recover()
:
If recover is called outside the deferred function it will not stop a panicking sequence.
在你的第二种情况下 recover()
本身就是延迟函数,显然 recover()
不会调用自己。所以这不会停止恐慌序列。
如果 recover()
本身会调用 recover()
,它会停止恐慌序列(但为什么要那样做?)。
另一个有趣的例子:
下面的代码也不死机(在Go Playground上试试):
package main
func main() {
var recover = func() { recover() }
defer recover()
panic("panic")
}
这里发生的是我们创建一个函数类型的 recover
变量,它的值是调用内置 recover()
函数的匿名函数。我们指定调用 recover
变量的值作为延迟函数,因此从中调用内置 recover()
停止恐慌序列。
据观察,这里真正的问题是 defer
的设计,因此答案应该是这样。
激发这个答案,defer
目前需要从 lambda 中取出一层嵌套堆栈,并且运行时使用此约束的特定副作用来确定是否 recover()
return是否为零。
这是一个例子:
func b() {
defer func() { if recover() != nil { fmt.Printf("bad") } }()
}
func a() {
defer func() {
b()
if recover() != nil {
fmt.Printf("good")
}
}()
panic("error")
}
b()
中的recover()
应该return为零。
在我看来,更好的选择是说 defer
将函数 BODY 或块作用域(而不是函数调用)作为其参数。那时,panic
和 recover()
return 值可以绑定到特定的堆栈帧,并且任何内部堆栈帧都会有一个 nil
平移上下文。因此,它看起来像这样:
func b() {
defer { if recover() != nil { fmt.Printf("bad") } }
}
func a() {
defer {
b()
if recover() != nil {
fmt.Printf("good")
}
}
panic("error")
}
在这一点上,很明显 a()
处于恐慌状态,但 b()
不是,并且 "being in the first stack frame of a deferred lambda" 之类的副作用对于正确实施运行时间。
所以,在这里违背常理:这不像预期的那样工作的原因是 go 语言中 defer
关键字的设计错误,该关键字是使用 non - 明显的实现细节副作用,然后这样编纂。