Go Panics
Understanding and handling runtime panics in Go
💥 What are Go Panics?
Panics are runtime errors that stop normal program execution. They occur when something goes seriously wrong, like accessing invalid memory. Unlike errors, panics should be rare and indicate programming bugs rather than expected conditions.
// This will cause a panic
func causePanic() {
var slice []int
fmt.Println(slice[0]) // Index out of range!
}
// Recover from panic
func safePanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
causePanic()
}
Output:
Recovered from panic: runtime error: index out of range [0] with length 0
Key Panic Concepts
Runtime Errors
Serious errors that stop execution
panic("something went wrong")
Recover
Catch and handle panics gracefully
if r := recover(); r != nil {}
Stack Unwinding
Deferred functions run during panic
defer cleanup()
panic("error")
Goroutine Scope
Panics affect only current goroutine
go func() {
panic("isolated")
}()
🔹 Common Panic Causes
Understanding what typically causes panics in Go:
package main
import "fmt"
func demonstratePanics() {
// 1. Index out of range
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic 1:", r)
}
}()
slice := []int{1, 2, 3}
fmt.Println(slice[10]) // This will panic
}
func nilPointerPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic 2:", r)
}
}()
var ptr *int
fmt.Println(*ptr) // Dereferencing nil pointer
}
func typePanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic 3:", r)
}
}()
var x interface{} = "hello"
num := x.(int) // Type assertion failure
fmt.Println(num)
}
func channelPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic 4:", r)
}
}()
ch := make(chan int)
close(ch)
ch <- 1 // Send on closed channel
}
func main() {
fmt.Println("Demonstrating common panics:")
demonstratePanics()
nilPointerPanic()
typePanic()
channelPanic()
fmt.Println("All panics recovered!")
}
Output:
Demonstrating common panics:
Panic 1: runtime error: index out of range [10] with length 3
Panic 2: runtime error: invalid memory address or nil pointer dereference
Panic 3: interface conversion: interface {} is string, not int
Panic 4: send on closed channel
All panics recovered!
🔹 Panic and Recover Pattern
The proper way to handle panics with recover:
func safeFunction(data []int, index int) (result int, err error) {
// Set up panic recovery
defer func() {
if r := recover(); r != nil {
// Convert panic to error
err = fmt.Errorf("panic occurred: %v", r)
result = 0
}
}()
// Potentially dangerous operation
result = data[index] * 2
return result, nil
}
func safeDivision(a, b float64) (result float64, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("division error: %v", r)
result = 0
}
}()
if b == 0 {
panic("division by zero")
}
return a / b, nil
}
func main() {
// Test safe function with valid input
if result, err := safeFunction([]int{1, 2, 3, 4, 5}, 2); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %d\n", result)
}
// Test safe function with invalid input
if result, err := safeFunction([]int{1, 2, 3}, 10); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %d\n", result)
}
// Test safe division
if result, err := safeDivision(10, 0); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Division result: %.2f\n", result)
}
}
Output:
Result: 6
Error: panic occurred: runtime error: index out of range [10] with length 3
Error: division error: division by zero
🔹 When to Use Panic
Appropriate use cases for panic in Go programs:
// 1. Initialization failures
func init() {
configFile := "app.config"
if _, err := os.Stat(configFile); os.IsNotExist(err) {
panic(fmt.Sprintf("Critical config file missing: %s", configFile))
}
}
// 2. Programming errors (assertions)
func processArray(arr []int) {
if len(arr) == 0 {
panic("processArray called with empty array - this should never happen")
}
// Process array...
for i, v := range arr {
fmt.Printf("Index %d: %d\n", i, v)
}
}
// 3. Unrecoverable state
type Database struct {
connected bool
}
func (db *Database) Query(sql string) []string {
if !db.connected {
panic("database connection lost - cannot continue")
}
// Simulate query
return []string{"result1", "result2"}
}
// 4. Library initialization
func NewCriticalService(config string) *CriticalService {
if config == "" {
panic("CriticalService requires non-empty config")
}
return &CriticalService{config: config}
}
type CriticalService struct {
config string
}
func main() {
// Good use: Programming assertion
defer func() {
if r := recover(); r != nil {
fmt.Printf("Caught panic: %v\n", r)
}
}()
// This will panic because it's a programming error
processArray([]int{}) // Empty array should never be passed
}
Output:
Caught panic: processArray called with empty array - this should never happen
🔹 Panic vs Error Guidelines
When to use panic vs regular error handling:
Use Panic When:
- Programming bugs: Conditions that should never occur
- Initialization failures: Critical setup that cannot continue
- Unrecoverable state: System is in invalid state
- Library misuse: API called incorrectly
Use Error When:
- Expected failures: Network timeouts, file not found
- User input errors: Invalid data from users
- External dependencies: Database connection issues
- Business logic: Validation failures
// Good: Use error for expected failures
func readUserFile(filename string) ([]byte, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read user file: %w", err)
}
return data, nil
}
// Good: Use panic for programming errors
func calculatePercentage(part, total int) float64 {
if total == 0 {
panic("calculatePercentage: total cannot be zero")
}
return float64(part) / float64(total) * 100
}
// Bad: Don't panic for user errors
func badParseAge(input string) int {
age, err := strconv.Atoi(input)
if err != nil {
panic("invalid age input") // Should return error instead
}
return age
}
// Good: Return error for user input
func goodParseAge(input string) (int, error) {
age, err := strconv.Atoi(input)
if err != nil {
return 0, fmt.Errorf("invalid age format: %s", input)
}
return age, nil
}