Go

Which of these recover calls actually stop the panic?

Question 502HardGo 1.22 to 1.25
func handle() {
    if r := recover(); r != nil {
        fmt.Println("recovered:", r)
    }
}

func a() { defer handle(); panic("A") }                 // 1
func b() { defer func() { handle() }(); panic("B") }    // 2
func c() { defer recover(); panic("C") }                // 3
func d() {                                              // 4
    defer func() { fmt.Println("recovered:", recover()) }()
    panic("D")
}

1 and 4 recover. 2 and 3 do not.

The spec says recover returns nil unless it is called directly by a deferred function while the goroutine is panicking. In 1, handle is the deferred function and calls recover directly. In 2, the deferred function is the anonymous closure, and recover sits one frame deeper inside handle, so it returns nil and the panic continues. In 3, recover is itself the deferred function. It is not called by a deferred function, so it does nothing. In 4, the closure calls it directly.

Other ways recover "fails": it only catches panics in the same goroutine, and it cannot catch fatal runtime errors such as concurrent map writes, running out of memory, or stack overflow. Since Go 1.21, panic(nil) turns into *runtime.PanicNilError, so recover() no longer returns nil for it.

More on Tricky Output & Code-Review Puzzles

All 38 Tricky Output & Code-Review Puzzles questions