Module: Error Handling

Panic and Recover

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 panic is 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 a deferred function.
  • If a panic is in progress, recover() stops the panicking and resumes normal execution.
  • recover() returns the value passed to panic(). If no panic is in progress, it returns nil.
  • If recover() is called outside a deferred function, it returns nil.

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:

  1. A deferred function is defined. This function will be executed when main exits, regardless of whether a panic occurs.
  2. Inside the deferred function, recover() is called.
  3. The second call to divide(5, 0) triggers a panic.
  4. The deferred function is executed. recover() catches the panic, retrieves the panic value ("Division by zero!"), and prints it.
  5. Execution continues after the deferred function, and the final fmt.Println statement is executed.

3. Best Practices & Considerations

  • Don't use panic for normal error handling. Use explicit error returns instead. panic should be reserved for truly exceptional situations where the program cannot continue safely.
  • Use defer with recover carefully. recover only works inside a deferred function. Make sure the deferred 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.Is and errors.As for 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: panic can 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.