Go

What happens with panic(nil) and with re-panicking? How did Go 1.21 change this?

Question 310HardGo 1.22 to 1.25

Before Go 1.21, panic(nil) made recover() return nil, so code that tested if r := recover(); r != nil couldn't tell "no panic" from "panicked with nil" and swallowed the panic silently. Since Go 1.21, panic(nil) (or a nil interface) turns into a panic with *runtime.PanicNilError, so recover returns a non-nil value. GODEBUG=panicnil=1 restores the old behavior.

func main() {
	defer func() {
		r := recover()
		fmt.Printf("%T: %v\n", r, r)
		// *runtime.PanicNilError: panic called with nil argument
	}()
	panic(nil)
}

Re-panicking: a deferred function that recovers can call panic again, either to escalate unknown panics or to add context. The new panic replaces the old one, and the crash output shows both, with the first marked [recovered].

defer func() {
	if r := recover(); r != nil {
		if pe, ok := r.(parseError); ok {
			err = pe.err // our own sentinel panic: convert to error
			return
		}
		panic(r) // not ours: let it crash
	}
}()

Go 1.25 made crash output clearer when a recovered panic is re-panicked with the same value: the value is printed once, marked [recovered, repanicked], instead of being repeated.

More on Error Handling & panics

All 37 Error Handling & panics questions