Go

Compare panic, os.Exit, log.Fatal and runtime.Goexit with respect to deferred functions.

Question 312HardGo 1.22 to 1.25
CallRuns defers?Effect
panic(v)Yes (current goroutine)Unwinds. Crashes with exit code 2 unless recovered.
os.Exit(n)NoExits immediately with code n. Buffers aren't flushed and defers don't run.
log.FatalNoLogs, then calls os.Exit(1).
log.PanicYesLogs, then panics.
runtime.Goexit()YesEnds 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

All 37 Error Handling & panics questions