Which of these recover calls actually stop the panic?
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
- Q500Is msg guaranteed to print "hello"? Compare unbuffered and buffered channels.
- Q501Code review: why does this timeout wrapper leak goroutines?
- Q503Does the recover in main protect this program?
- Q504A deferred function panics while another panic is in progress. What prints?
- Q505What does this integer puzzle print?
- Q506Why do these two floating-point comparisons give different results?