Go

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

All 37 Error Handling & panics questions