Go Panic and Recover
Panic and recover in Go are mechanisms for handling exceptional circumstances that cannot be handled with regular error returns. While Go's philosophy emphasizes explicit error handling, panic and recover provide a way to handle truly exceptional situations, such as programming errors, unrecoverable failures, or critical system errors. Understanding when and how to use panic and recover is crucial for writing robust Go applications that can handle unexpected failures gracefully.
Understanding Panic and Recover
What Are Panic and Recover?
Panic and recover in Go are mechanisms for handling exceptional circumstances:
Panic
Panic is a built-in function that stops the normal execution of a goroutine and begins panicking.
Recover
Recover is a built-in function that regains control of a panicking goroutine and allows it to continue execution.
When to Use Panic vs Regular Errors
Use Panic For:
- Programming errors - Index out of bounds, nil pointer dereference
- Unrecoverable failures - Critical system failures, configuration errors
- Impossible conditions - Logic errors that should never occur
Use Regular Errors For:
- Expected failures - File not found, network timeout, validation errors
- Recoverable conditions - Temporary failures, user input errors
- Business logic errors - Invalid operations, insufficient permissions
Panic Mechanism
Understanding Panic
Panic Behavior
When panic is called, the current function stops executing and begins panicking.
Panic Propagation
Panic propagates up the call stack until it reaches a recover or the program terminates.
package main
import "fmt"
func main() {
// Panic mechanism examples
fmt.Println("Panic mechanism examples:")
// Basic panic
func basicPanic() {
fmt.Println("Before panic")
panic("something went wrong")
fmt.Println("After panic") // This will never execute
}
// Call basic panic (this will terminate the program)
// basicPanic()
// Panic with different types
func panicWithString() {
panic("string panic message")
}
func panicWithError() {
panic(fmt.Errorf("error panic message"))
}
func panicWithInt() {
panic(42)
}
// Panic in nested functions
func level3() {
fmt.Println("Level 3: about to panic")
panic("panic from level 3")
}
func level2() {
fmt.Println("Level 2: calling level 3")
level3()
fmt.Println("Level 2: after level 3") // This will never execute
}
func level1() {
fmt.Println("Level 1: calling level 2")
level2()
fmt.Println("Level 1: after level 2") // This will never execute
}
// Call nested panic (this will terminate the program)
// level1()
// Panic with defer
func panicWithDefer() {
defer fmt.Println("Defer: cleanup before panic")
fmt.Println("Before panic")
panic("panic with defer")
fmt.Println("After panic") // This will never execute
}
// Call panic with defer (this will terminate the program)
// panicWithDefer()
// Multiple defers with panic
func multipleDefersWithPanic() {
defer fmt.Println("Defer 1: first defer")
defer fmt.Println("Defer 2: second defer")
defer fmt.Println("Defer 3: third defer")
fmt.Println("Before panic")
panic("panic with multiple defers")
fmt.Println("After panic") // This will never execute
}
// Call multiple defers with panic (this will terminate the program)
// multipleDefersWithPanic()
// Panic in loops
func panicInLoop() {
for i := 0; i < 5; i++ {
defer fmt.Printf("Defer in loop: %d\n", i)
if i == 2 {
panic(fmt.Sprintf("panic at iteration %d", i))
}
fmt.Printf("Loop iteration: %d\n", i)
}
}
// Call panic in loop (this will terminate the program)
// panicInLoop()
fmt.Println("Panic mechanism examples completed")
}
Panic with Defer
Defer Execution During Panic
Defer statements are executed even when panic occurs.
Defer Order During Panic
Defer statements are executed in LIFO (Last In, First Out) order.
package main
import "fmt"
func main() {
// Panic with defer examples
fmt.Println("Panic with defer examples:")
// Defer execution during panic
func deferDuringPanic() {
defer fmt.Println("Defer 1: cleanup")
defer fmt.Println("Defer 2: logging")
defer fmt.Println("Defer 3: final cleanup")
fmt.Println("Before panic")
panic("panic with defer")
fmt.Println("After panic") // This will never execute
}
// Call defer during panic (this will terminate the program)
// deferDuringPanic()
// Defer with panic recovery
func deferWithRecovery() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
defer fmt.Println("Defer: cleanup")
fmt.Println("Before panic")
panic("panic with recovery")
fmt.Println("After panic") // This will never execute
}
// Call defer with recovery
deferWithRecovery()
fmt.Println("Function completed successfully")
// Output:
// Before panic
// Defer: cleanup
// Recovered from panic: panic with recovery
// Function completed successfully
// Multiple defers with recovery
func multipleDefersWithRecovery() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
defer fmt.Println("Defer 1: first cleanup")
defer fmt.Println("Defer 2: second cleanup")
defer fmt.Println("Defer 3: third cleanup")
fmt.Println("Before panic")
panic("panic with multiple defers and recovery")
fmt.Println("After panic") // This will never execute
}
// Call multiple defers with recovery
multipleDefersWithRecovery()
fmt.Println("Multiple defers function completed successfully")
// Output:
// Before panic
// Defer 3: third cleanup
// Defer 2: second cleanup
// Defer 1: first cleanup
// Recovered from panic: panic with multiple defers and recovery
// Multiple defers function completed successfully
// Defer with conditional recovery
func deferWithConditionalRecovery() {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
fmt.Printf("Recovered from string panic: %s\n", v)
case error:
fmt.Printf("Recovered from error panic: %v\n", v)
default:
fmt.Printf("Recovered from unknown panic: %v\n", v)
}
}
}()
defer fmt.Println("Defer: conditional cleanup")
fmt.Println("Before panic")
panic("string panic message")
fmt.Println("After panic") // This will never execute
}
// Call defer with conditional recovery
deferWithConditionalRecovery()
fmt.Println("Conditional recovery function completed successfully")
// Output:
// Before panic
// Defer: conditional cleanup
// Recovered from string panic: string panic message
// Conditional recovery function completed successfully
}
Recover Mechanism
Understanding Recover
Recover Function
Recover is a built-in function that regains control of a panicking goroutine.
Recover Behavior
Recover only works when called from within a deferred function.
package main
import "fmt"
func main() {
// Recover mechanism examples
fmt.Println("Recover mechanism examples:")
// Basic recover
func basicRecover() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
fmt.Println("Before panic")
panic("basic panic")
fmt.Println("After panic") // This will never execute
}
// Call basic recover
basicRecover()
fmt.Println("Basic recover function completed")
// Output:
// Before panic
// Recovered from panic: basic panic
// Basic recover function completed
// Recover with return value
func recoverWithReturn() (result string, err error) {
defer func() {
if r := recover(); r != nil {
result = "recovered"
err = fmt.Errorf("panic recovered: %v", r)
}
}()
fmt.Println("Before panic")
panic("panic with return value")
fmt.Println("After panic") // This will never execute
return "success", nil
}
// Call recover with return value
result, err := recoverWithReturn()
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
// Output:
// Before panic
// Error: panic recovered: panic with return value
// Result: recovered
// Recover with different panic types
func recoverWithTypes() {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
fmt.Printf("Recovered from string panic: %s\n", v)
case error:
fmt.Printf("Recovered from error panic: %v\n", v)
case int:
fmt.Printf("Recovered from int panic: %d\n", v)
default:
fmt.Printf("Recovered from unknown panic: %v (type: %T)\n", v, v)
}
}
}()
fmt.Println("Before panic")
panic(42)
fmt.Println("After panic") // This will never execute
}
// Call recover with types
recoverWithTypes()
fmt.Println("Recover with types function completed")
// Output:
// Before panic
// Recovered from int panic: 42
// Recover with types function completed
// Recover with conditional handling
func recoverWithConditionalHandling() {
defer func() {
if r := recover(); r != nil {
if str, ok := r.(string); ok && str == "recoverable" {
fmt.Println("Recovered from recoverable panic")
} else {
fmt.Printf("Recovered from unrecoverable panic: %v\n", r)
// Re-panic for unrecoverable errors
panic(r)
}
}
}()
fmt.Println("Before panic")
panic("recoverable")
fmt.Println("After panic") // This will never execute
}
// Call recover with conditional handling
recoverWithConditionalHandling()
fmt.Println("Conditional handling function completed")
// Output:
// Before panic
// Recovered from recoverable panic
// Conditional handling function completed
}
Recover Patterns
Recover in Functions
Using recover within functions to handle panics gracefully.
Recover with Error Returns
Converting panics to errors for consistent error handling.
package main
import (
"fmt"
"strconv"
)
func main() {
// Recover patterns examples
fmt.Println("Recover patterns examples:")
// Recover with error return
func safeDivide(a, b float64) (float64, error) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic in safeDivide: %v\n", r)
}
}()
if b == 0 {
panic("division by zero")
}
return a / b, nil
}
// Test safe divide
result, err := safeDivide(10, 2)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %.2f\n", result)
}
// Output: Result: 5.00
result, err = safeDivide(10, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %.2f\n", result)
}
// Output: Recovered from panic in safeDivide: division by zero
// Error: <nil>
// Result: 0.00
// Recover with proper error handling
func safeDivideWithError(a, b float64) (float64, error) {
var result float64
var err error
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic in safeDivideWithError: %v", r)
result = 0
}
}()
if b == 0 {
panic("division by zero")
}
result = a / b
return result, nil
}
// Test safe divide with error
result, err = safeDivideWithError(10, 2)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %.2f\n", result)
}
// Output: Result: 5.00
result, err = safeDivideWithError(10, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %.2f\n", result)
}
// Output: Error: panic in safeDivideWithError: division by zero
// Result: 0.00
// Recover with logging
func safeOperation(operation func() error) error {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Operation panicked: %v\n", r)
// Log the panic for debugging
fmt.Println("Panic logged for debugging")
}
}()
return operation()
}
// Test safe operation
err = safeOperation(func() error {
panic("operation failed")
return nil
})
if err != nil {
fmt.Printf("Operation error: %v\n", err)
} else {
fmt.Println("Operation completed successfully")
}
// Output:
// Operation panicked: operation failed
// Panic logged for debugging
// Operation completed successfully
// Recover with retry
func safeOperationWithRetry(operation func() error, maxRetries int) error {
for i := 0; i <= maxRetries; i++ {
func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Attempt %d panicked: %v\n", i+1, r)
}
}()
if err := operation(); err != nil {
fmt.Printf("Attempt %d failed: %v\n", i+1, err)
return
}
fmt.Printf("Attempt %d succeeded\n", i+1)
}()
}
return fmt.Errorf("operation failed after %d attempts", maxRetries+1)
}
// Test safe operation with retry
err = safeOperationWithRetry(func() error {
if true { // Simulate failure
panic("simulated panic")
}
return nil
}, 2)
if err != nil {
fmt.Printf("Retry error: %v\n", err)
} else {
fmt.Println("Retry operation completed successfully")
}
// Output:
// Attempt 1 panicked: simulated panic
// Attempt 2 panicked: simulated panic
// Attempt 3 panicked: simulated panic
// Retry error: operation failed after 3 attempts
}
Best Practices
When to Use Panic
Appropriate Use Cases
Understanding when panic is the right choice.
Inappropriate Use Cases
Understanding when panic should not be used.
package main
import (
"fmt"
"strconv"
)
func main() {
// Best practices examples
fmt.Println("Best practices examples:")
// Appropriate use of panic: programming errors
func getElement(slice []int, index int) int {
if index < 0 || index >= len(slice) {
panic(fmt.Sprintf("index out of bounds: %d (length: %d)", index, len(slice)))
}
return slice[index]
}
// Test appropriate panic
numbers := []int{1, 2, 3, 4, 5}
// This will panic (appropriate)
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from appropriate panic: %v\n", r)
}
}()
// getElement(numbers, 10) // This would panic
// Appropriate use of panic: unrecoverable configuration errors
func loadConfiguration() map[string]string {
// Simulate configuration loading
config := make(map[string]string)
// Critical configuration missing
if config["database_url"] == "" {
panic("critical configuration missing: database_url")
}
return config
}
// Test configuration panic
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from configuration panic: %v\n", r)
}
}()
// loadConfiguration() // This would panic
// Inappropriate use of panic: user input validation
func validateUserInput(input string) error {
if input == "" {
return fmt.Errorf("input cannot be empty")
}
if len(input) < 3 {
return fmt.Errorf("input too short")
}
return nil
}
// Test inappropriate panic (don't do this)
func badValidateUserInput(input string) {
if input == "" {
panic("input cannot be empty") // This is inappropriate
}
}
// Good: use error return
err := validateUserInput("")
if err != nil {
fmt.Printf("Validation error: %v\n", err)
} else {
fmt.Println("Input is valid")
}
// Output: Validation error: input cannot be empty
// Inappropriate use of panic: network errors
func makeNetworkRequest() error {
// Simulate network error
return fmt.Errorf("network timeout")
}
// Test network error handling
err = makeNetworkRequest()
if err != nil {
fmt.Printf("Network error: %v\n", err)
} else {
fmt.Println("Network request successful")
}
// Output: Network error: network timeout
// Appropriate use of panic: impossible conditions
func processUser(userID string) {
if userID == "" {
panic("userID cannot be empty - this should never happen")
}
// Process user
fmt.Printf("Processing user: %s\n", userID)
}
// Test impossible condition panic
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from impossible condition panic: %v\n", r)
}
}()
// processUser("") // This would panic
// Best practice: panic recovery at the right level
func processWithRecovery() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
// Log the panic for debugging
fmt.Println("Panic logged for debugging")
}
}()
// Process operations that might panic
fmt.Println("Processing operations")
// Simulate operations that might panic
}
// Call process with recovery
processWithRecovery()
fmt.Println("Process with recovery completed")
// Output:
// Processing operations
// Process with recovery completed
// Best practice: convert panic to error when appropriate
func safeOperation() error {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Converted panic to error: %v\n", r)
}
}()
// Simulate operation that might panic
panic("operation failed")
return nil
}
// Test safe operation
err = safeOperation()
if err != nil {
fmt.Printf("Safe operation error: %v\n", err)
} else {
fmt.Println("Safe operation completed successfully")
}
// Output: Converted panic to error: operation failed
// Safe operation completed successfully
}
Recovery Strategies
Recovery at the Right Level
Understanding where to place recovery code.
Recovery with Logging
Logging panics for debugging and monitoring.
package main
import (
"fmt"
"log"
"os"
)
func main() {
// Recovery strategies examples
fmt.Println("Recovery strategies examples:")
// Recovery with logging
func recoverWithLogging() {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic recovered: %v\n", r)
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
fmt.Println("Before panic")
panic("panic with logging")
fmt.Println("After panic") // This will never execute
}
// Call recover with logging
recoverWithLogging()
fmt.Println("Recovery with logging completed")
// Output:
// Before panic
// Recovered from panic: panic with logging
// Recovery with logging completed
// Recovery with file logging
func recoverWithFileLogging() {
defer func() {
if r := recover(); r != nil {
// Log to file
file, err := os.OpenFile("panic.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("Failed to open log file: %v\n", err)
} else {
defer file.Close()
fmt.Fprintf(file, "Panic recovered: %v\n", r)
}
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
fmt.Println("Before panic")
panic("panic with file logging")
fmt.Println("After panic") // This will never execute
}
// Call recover with file logging
recoverWithFileLogging()
fmt.Println("Recovery with file logging completed")
// Output:
// Before panic
// Recovered from panic: panic with file logging
// Recovery with file logging completed
// Recovery with cleanup
func recoverWithCleanup() {
var resources []string
defer func() {
// Cleanup resources
for _, resource := range resources {
fmt.Printf("Cleaning up resource: %s\n", resource)
}
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
}
}()
// Simulate resource allocation
resources = append(resources, "database connection")
resources = append(resources, "file handle")
resources = append(resources, "network connection")
fmt.Println("Before panic")
panic("panic with cleanup")
fmt.Println("After panic") // This will never execute
}
// Call recover with cleanup
recoverWithCleanup()
fmt.Println("Recovery with cleanup completed")
// Output:
// Before panic
// Cleaning up resource: database connection
// Cleaning up resource: file handle
// Cleaning up resource: network connection
// Recovered from panic: panic with cleanup
// Recovery with cleanup completed
// Recovery with error conversion
func recoverWithErrorConversion() error {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Converted panic to error: %v\n", r)
}
}()
fmt.Println("Before panic")
panic("panic with error conversion")
fmt.Println("After panic") // This will never execute
return nil
}
// Test recovery with error conversion
err := recoverWithErrorConversion()
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Recovery with error conversion completed")
}
// Output:
// Before panic
// Converted panic to error: panic with error conversion
// Recovery with error conversion completed
}
What You've Learned
Congratulations! You now have a comprehensive understanding of Go's panic and recover mechanisms:
Panic Mechanism
- Understanding how panic stops normal execution and begins panicking
- Learning about panic propagation through the call stack
- Understanding defer execution during panic
- Learning about different types of panic values
Recover Mechanism
- Understanding how recover regains control of panicking goroutines
- Learning about recover behavior and requirements
- Understanding recover patterns and best practices
- Learning about converting panics to errors
Best Practices
- Understanding when to use panic vs regular errors
- Learning appropriate and inappropriate use cases for panic
- Understanding recovery strategies and cleanup
- Learning about panic logging and monitoring
Key Concepts
panic
- Built-in function that stops normal executionrecover
- Built-in function that regains control of panicking goroutines- Panic propagation - How panic flows up the call stack
- Defer execution - How defer statements execute during panic
- Recovery strategies - Best practices for handling panics
Next Steps
You now have a solid foundation in Go's panic and recover mechanisms. These concepts complete your understanding of Go's error handling system, which includes regular error handling, custom error types, and exceptional circumstances.
Understanding panic and recover is crucial for building robust applications that can handle unexpected failures gracefully. These concepts form the foundation for all the more advanced programming techniques we'll cover in the coming chapters.
Ready to learn about concurrency? Let's explore Go's powerful concurrency features and learn how to build concurrent applications with goroutines and channels!