Go Programming: Error Handling - Panic and Recover
Go's error handling philosophy emphasizes explicit error checking. However, sometimes unexpected or unrecoverable errors occur. For these situations, Go provides panic and recover mechanisms. They are not the primary way to handle errors, but are useful for dealing with truly exceptional situations.
1. Panic
panic is a built-in function that signals a runtime error. When a panic occurs, normal program execution stops. It's like an unhandled exception in other languages.
How it works:
panic()can be called with any value as an argument. This value (often a string or an error) is the panic value.- When
panicis called, the current function immediately stops executing. - Go unwinds the call stack, executing any deferred functions along the way.
- If the panic is not
recovered (explained below), the program terminates and prints a stack trace to the console.
Example:
package main
import "fmt"
func divide(a, b int) {
if b == 0 {
panic("Division by zero!") // Trigger a panic
}
fmt.Println(a / b)
}
func main() {
divide(10, 2)
divide(5, 0) // This will cause a panic
fmt.Println("This line will not be executed")
}
Output:
5
panic: Division by zero!
goroutine 1 [running]:
main.divide(0x5, 0x0)
/tmp/sandbox12345/prog.go:6 +0x45
main.main()
/tmp/sandbox12345/prog.go:11 +0x25
exit status 2
In this example, the second call to divide with b=0 triggers the panic. The program terminates after printing the stack trace. The final fmt.Println statement is never reached.
2. Recover
recover is a built-in function that allows you to regain control of a panicking goroutine. It's only useful inside a deferred function.
How it works:
recover()is called inside adeferred function.- If a panic is in progress,
recover()stops the panicking and resumes normal execution. recover()returns the value passed topanic(). If no panic is in progress, it returnsnil.- If
recover()is called outside adeferred function, it returnsnil.
Example:
package main
import "fmt"
func divide(a, b int) {
if b == 0 {
panic("Division by zero!")
}
fmt.Println(a / b)
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
divide(10, 2)
divide(5, 0) // This will cause a panic, but will be recovered
fmt.Println("This line will be executed")
}
Output:
5
Recovered from panic: Division by zero!
This line will be executed
In this example:
- A
deferred function is defined. This function will be executed whenmainexits, regardless of whether a panic occurs. - Inside the
deferred function,recover()is called. - The second call to
divide(5, 0)triggers a panic. - The
deferred function is executed.recover()catches the panic, retrieves the panic value ("Division by zero!"), and prints it. - Execution continues after the
deferred function, and the finalfmt.Printlnstatement is executed.
3. Best Practices & Considerations
- Don't use
panicfor normal error handling. Use explicit error returns instead.panicshould be reserved for truly exceptional situations where the program cannot continue safely. - Use
deferwithrecovercarefully.recoveronly works inside adeferred function. Make sure thedeferred function is placed in a scope where it can catch panics from the code it's intended to protect. - Log panics. Even if you recover from a panic, it's important to log the panic value and stack trace for debugging purposes.
- Avoid recovering from panics in library code. If a library function panics, it's often better to let the panic propagate to the caller, allowing the caller to handle it appropriately. Recovering in a library can mask errors and make debugging difficult.
- Consider using
errors.Isanderrors.Asfor more robust error handling. These functions help you identify specific error types and extract information from errors.
When to use panic and recover:
- Unrecoverable configuration errors: If the program cannot start due to a critical configuration error.
- Nil pointer dereferences in critical sections: If a nil pointer dereference occurs in a part of the code that must be working correctly.
- Internal inconsistencies: If the program detects an internal inconsistency that indicates a bug.
- Testing:
paniccan be used in tests to verify that certain conditions cause the program to fail.
Summary
panic and recover are powerful tools for handling exceptional situations in Go. However, they should be used sparingly and with caution. Prioritize explicit error handling with return values for most error conditions. Use panic and recover only when a truly unrecoverable error occurs and you need to regain control of a panicking goroutine. Remember to log panics for debugging and avoid recovering in library code unless absolutely necessary.