Yesterday we saw how panic plays with deferred functions, in terms of order of execution.
Today we’ll take things up one level, and throw recover in there… there’s more to recover than just execution order. We’ll get to that next.
Handling panics
…
The
recoverfunction allows a program to manage behavior of a panicking goroutine. Suppose a functionGdefers a functionDthat callsrecoverand a panic occurs in a function on the same goroutine in whichGis executing. When the running of deferred functions reachesD, the return value ofD’s call torecoverwill be the value passed to the call ofpanic. IfDreturns normally, without starting a newpanic, the panicking sequence stops. In that case, the state of functions called betweenGand the call topanicis discarded, and normal execution resumes. Any functions deferred byGbeforeDare then run andG’s execution terminates by returning to its caller.
Let’s demonstrate this order of execution by expanding on yesterday’s code example:
func main() {
foo()
}
func foo() {
defer fmt.Println("foo is done")
bar()
}
func bar() {
defer fmt.Println("bar is done")
baz()
}
func baz() {
defer fmt.Println("baz is done")
panic(123)
}
As written, this code outputs:
baz is done
bar is done
foo is done
panic: 123
followed by a stack trace.
Let’s now add a recovery, and see how this affect things.
func main() {
foo()
}
func foo() {
defer fmt.Println("foo is done")
bar()
}
func bar() {
defer fmt.Println("bar is done")
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered", r)
}
}()
defer fmt.Println("an extra defer for good measure")
baz()
}
func baz() {
defer fmt.Println("baz is done")
panic(123)
}
I also threw in an extra defer for good measure. 😉 Run the code yourself to see that now the output is as follows:
baz is done
an extra defer for good measure
recovered 123
bar is done
foo is done
Two things to point out here. First, and most important, there’s no panic! The panic was recovered, and execution was permitted to continue as normal from the recovery point, without the program exiting. This is one other thing that functions like log.Fatal, doesn’t allow—there’s no opportunity to recover from a log.Fatal!
Second, notice the order of the deferred statement executions. They execute in the opposite order declared, even considering the recovery.
Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)