Handling panics
…
While executing a function
F
, an explicit call topanic
or a run-time panic terminates the execution ofF
. Any functions deferred byF
are then executed as usual. Next, any deferred functions run byF
’s caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument topanic
. This termination sequence is called panicking.panic(42) panic("unreachable") panic(Error("cannot parse"))
This sounds pretty straight forward. And I think it is. But let’s just look at a simple example to drive it all home.
func main() {
foo()
}
func foo() {
defer fmt.Println("foo is done")
bar()
}
func bar() {
defer fmt.Println("bar is done")
panic(123)
}
When we run this program, the output is:
bar is done
foo is done
panic: 123
goroutine 1 [running]:
main.bar()
/tmp/sandbox644471523/prog.go:18 +0x65
main.foo()
/tmp/sandbox644471523/prog.go:13 +0x52
main.main()
/tmp/sandbox644471523/prog.go:8 +0xf
Here we see that the deferred functions were run, in reverse order of having been deferred, and then finally we see the panic value (123
), and a stack trace.
There’s one related topic worth discussing, even though I’ve mentioned it before, because it relates to this: If you call os.Exit
(or other functions that in turn calls it, such as log.Fatal
), the deferred functions do not run!
func main() {
foo()
}
func foo() {
defer fmt.Println("foo is done")
bar()
}
func bar() {
defer fmt.Println("bar is done")
os.Exit(1)
}
When we run this version of the program, the output is empty! No deferred functions are executed, and there’s no chance to recover (the topic we start on tomorrow).
Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)