为什么 `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 或块作用域(而不是函数调用)作为其参数。那时,panicrecover() 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 - 明显的实现细节副作用,然后这样编纂。