How do you convert a panic into an error returned from a function?
Question 309MediumGo 1.22 to 1.25
Use a named result and assign it inside a deferred function that calls recover. This is the only way a deferred function can change what the caller receives.
func SafeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
if re, ok := r.(runtime.Error); ok {
err = fmt.Errorf("runtime panic: %w", re)
return
}
err = fmt.Errorf("panic: %v", r)
}
}()
fn()
return nil
}
func main() {
err := SafeCall(func() {
var m map[string]int
m["x"] = 1 // panic: assignment to entry in nil map
})
fmt.Println(err)
var re runtime.Error
fmt.Println(errors.As(err, &re)) // true
}
Points to raise: recover() returns any. The value may be a runtime.Error, an error, a string, or anything else, so wrap it carefully. Capture debug.Stack() inside the deferred function, because the stack is still intact there. Don't use this to hide bugs everywhere. Use it at trust boundaries: plugin execution, request handlers, job runners.
More on Error Handling & panics
- Q307A goroutine panics. Can a
recoverinmaincatch it? How do you protect background goroutines? - Q308What does this print? (Defer order during panic)
- 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? - Q312Compare
panic,os.Exit,log.Fatalandruntime.Goexitwith respect to deferred functions. - Q313How do you propagate errors from multiple goroutines? Explain
errgroup.