Go > Error Handling > Panic and Recover > Using panic
Panic and Recover: Safe Division
This code snippet demonstrates how to use `panic` and `recover` to handle errors during division, preventing program crashes and providing a graceful recovery mechanism. It illustrates a common use case for `panic` when encountering unexpected or unrecoverable situations.
The core concept: Safe Division with Panic and Recover
This example defines a function `safeDivide` that performs division. If the denominator is zero, it calls `panic` with an error message. The `main` function uses `defer` and `recover` to catch any panics that occur during the execution of `safeDivide`. When a panic occurs, `recover` captures the panic value, logs it, and allows the program to continue execution from the point after the `panic` call within `safeDivide`. Critically, if `recover` isn't called in a `defer`red function higher up the call stack, the program crashes.
package main
import (
"fmt"
"log"
)
func safeDivide(numerator, denominator int) int {
if denominator == 0 {
panic("division by zero")
}
return numerator / denominator
}
func main() {
defer func() {
if r := recover(); r != nil {
log.Println("Recovered from panic:", r)
// Optionally, take corrective action here.
fmt.Println("Program continues after error.")
}
}()
result := safeDivide(10, 2)
fmt.Println("Result:", result)
// This will cause a panic
result = safeDivide(5, 0)
fmt.Println("Result:", result) // This line will not be executed
fmt.Println("Program continues...") // This line will be executed because of recover
}
Explanation of the Code
The `safeDivide` function checks for division by zero. If the denominator is zero, it calls `panic`. The `panic` function immediately stops the normal execution of the function, unwinds the stack, and executes any deferred functions. The `defer` statement in `main` registers a function that will be executed when `main` exits, either normally or due to a panic. Inside the deferred function, `recover` is called. If a panic occurred, `recover` returns the value passed to `panic`. If no panic occurred, `recover` returns `nil`. By checking the return value of `recover`, we can determine whether a panic occurred and take appropriate action. In this case, we log the error message and print a message indicating that the program continues after the error.
Concepts Behind the Snippet
panic
is a built-in function that stops the normal execution of the current goroutine. When a function F calls panic
, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic
. The process continues up the stack until all functions in the executing goroutine have returned, at which point the program crashes. However, panic
can be intercepted by using the built-in recover
function. recover
stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic
. It's important to note that recover
only works effectively when called within a defer
red function.
Real-Life Use Case Section
A real-life use case for panic
and recover
is within a web server. Consider a handler that processes user requests. If a request triggers a critical error that would otherwise crash the server (e.g., accessing a resource that is unexpectedly unavailable), panic
can be used to signal the error. A higher-level recovery mechanism (often in middleware) can catch the panic, log the error, and return an appropriate error response to the user, preventing the entire server from crashing. Another example could be in a critical data processing pipeline where an unexpected data format can corrupt the entire pipeline. Panicking and recovering can isolate the corrupt data and allow the pipeline to continue processing other data.
package main
import (
"fmt"
"log"
"net/http"
)
func riskyOperation() {
panic("Something went terribly wrong!")
}
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Println("Recovered panic:", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}()
riskyOperation()
fmt.Fprintln(w, "Request processed successfully.")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Best Practices
panic
sparingly: Prefer returning errors using the error
interface for recoverable errors. Use panic
only for truly exceptional situations that should never occur under normal circumstances.recover
only at the top level: It's generally best to recover
only in the top-level function of a goroutine (e.g., the main
function or the entry point of a worker goroutine). This prevents unexpected side effects from recover
ing in deeply nested functions.
Interview Tip
Be prepared to explain the difference between errors and panics. Errors are used to indicate recoverable conditions, while panics are used to indicate unrecoverable conditions. You should also be able to describe how defer
and recover
work together to handle panics gracefully. Be prepared to discuss scenarios where panic/recover is a suitable solution.
When to use them
Use panic
when a program encounters a situation it cannot reasonably recover from. Examples include:
Use recover
to prevent the entire program from crashing when a panic occurs, particularly in long-running services or applications.
Memory footprint
The memory footprint of panic
and recover
is generally small. When a panic occurs, the Go runtime unwinds the stack, executing deferred functions along the way. The memory overhead is mainly due to the deferred function calls and the storage of the panic value. However, excessive use of panic
and recover
can potentially impact performance, especially if panics occur frequently, as stack unwinding is a relatively expensive operation compared to normal error handling.
Alternatives
The primary alternative to using panic
and recover
is to use the standard error handling mechanism (returning error
values). For most situations, returning errors is the preferred approach. Consider using custom error types to provide more context about the error.
Pros
Cons
recover
is not used).
FAQ
-
What happens if I don't call `recover` after a `panic`?
If you don't call `recover` in a deferred function, the panic will continue to propagate up the call stack until it reaches the top-level function of the goroutine. At that point, the program will terminate and print a stack trace. -
Can I recover from a panic in a different goroutine?
No, `recover` can only catch panics that occur in the same goroutine. Panics do not cross goroutine boundaries. -
Is it good practice to use panic/recover for all error handling?
No, it's generally not a good practice. Use errors for normal, recoverable conditions, and reserve `panic` for truly exceptional situations that should never occur. Overuse of panic/recover can make code harder to understand and debug.