Compare panic, os.Exit, log.Fatal and runtime.Goexit with respect to deferred functions.
Question 312HardGo 1.22 to 1.25
| Call | Runs defers? | Effect |
|---|---|---|
panic(v) | Yes (current goroutine) | Unwinds. Crashes with exit code 2 unless recovered. |
os.Exit(n) | No | Exits immediately with code n. Buffers aren't flushed and defers don't run. |
log.Fatal | No | Logs, then calls os.Exit(1). |
log.Panic | Yes | Logs, then panics. |
runtime.Goexit() | Yes | Ends only the calling goroutine. It is not a panic, and recover returns nil. |
func main() {
defer fmt.Println("deferred") // NOT printed
if err := run(); err != nil {
log.Fatal(err)
}
}
// Idiomatic: keep defers in run(), exit only in main.
func main2() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
t.FailNow and t.Fatal use runtime.Goexit. That is why they have to be called from the test goroutine, not from goroutines the test spawns. If Goexit runs in the main goroutine, the program continues until the other goroutines finish and then crashes with a deadlock.
More on Error Handling & panics
- Q310What happens with
panic(nil)and with re-panicking? How did Go 1.21 change this? - Q311Which runtime failures can't be recovered with
recover? - Q313How do you propagate errors from multiple goroutines? Explain
errgroup. - Q314How do you collect all errors from concurrent workers rather than just the first?
- Q315Go errors don't carry stack traces. How do you get stack information when you need it?
- Q316"Handle an error only once." What does that mean, and what is wrong with log-and-return?