What are the rules for recover? Why doesn't defer recover() stop a panic?
Question 18HardGo 1.22 to 1.25
recover stops a panic only when it is called directly by a deferred function while the goroutine is panicking. In every other situation it returns nil and does nothing.
func a() {
defer recover() // does NOT recover: recover is the deferred function itself,
panic("x") // it is not called BY a deferred function
}
func helper() { recover() }
func b() {
defer func() { helper() }() // does NOT recover: called one level too deep
panic("x")
}
func c() {
defer func() {
if r := recover(); r != nil { // works
log.Println("recovered:", r)
}
}()
panic("x")
}
Other important points:
- A panic only unwinds its own goroutine. A panic in a goroutine that nobody recovers crashes the whole program. A
recoverinmaincan't catch it, so every goroutine you start needs its own recovery if you want protection. - After recovering, the function returns normally. Set named results if the caller needs to know something went wrong.
- Since Go 1.21,
panic(nil)becomes a*runtime.PanicNilError, sorecover()never returns nil during a real panic. - Some fatal errors can't be recovered at all: concurrent map writes, running out of memory, deadlock, and stack overflow.
More on Language Fundamentals & Types
- Q16How can a deferred function change a function's return value? What do
f()andg()return? - Q17What does this print? (defer with value vs pointer receivers)
- Q19Do deferred functions run on
os.Exit,log.Fatal,runtime.Goexitandpanic? - Q20In what order are package-level variables initialized? What does this print?
- Q21How does initialization work across packages, and what are the rules for
init()? - Q22How do labeled
breakandcontinuework? Why does a plainbreakinsideselectnot leave the loop?