1. go
  2. /basics
  3. /defer-panic

Understanding Defer, Panic, and Recover in Go Programming

Go provides powerful mechanisms for handling cleanup operations and runtime errors through defer, panic, and recover. This guide covers these essential concepts in detail.

Defer Statement

Basic Defer Usage

// Basic defer example
func readFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // Will be called when function returns

    // Read file contents...
    return nil
}

Multiple Defers

func multipleDefers() {
    fmt.Println("Start")
    defer fmt.Println("First defer")
    defer fmt.Println("Second defer")
    defer fmt.Println("Third defer")
    fmt.Println("End")
}
// Output:
// Start
// End
// Third defer
// Second defer
// First defer

Defer with Arguments

func deferWithArgs() {
    i := 0
    defer fmt.Println("Deferred value:", i)
    i = 1
    fmt.Println("Final value:", i)
}
// Output:
// Final value: 1
// Deferred value: 0

Panic

Basic Panic Usage

func divide(a, b int) int {
    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

func main() {
    result := divide(10, 0)  // This will panic
    fmt.Println(result)      // Never reached
}

Custom Panic Values

type CustomError struct {
    Code    int
    Message string
}

func processData(data []byte) {
    if len(data) == 0 {
        panic(CustomError{
            Code:    500,
            Message: "empty data",
        })
    }
    // Process data...
}

Recover

Basic Recovery

func recoverExample() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Recovered from panic: %v\n", r)
        }
    }()

    panic("something went wrong")
    fmt.Println("This line never executes")
}

Selective Recovery

func selectiveRecover() {
    defer func() {
        if r := recover(); r != nil {
            switch x := r.(type) {
            case string:
                fmt.Printf("Recovered string panic: %s\n", x)
            case error:
                fmt.Printf("Recovered error panic: %v\n", x)
            default:
                panic(r)  // Re-panic for unknown types
            }
        }
    }()

    // Your code here...
}

Best Practices

1. Resource Cleanup

// Good: Proper resource cleanup
func processFiles(filenames []string) error {
    for _, filename := range filenames {
        if err := processFile(filename); err != nil {
            return fmt.Errorf("processing %s: %w", filename, err)
        }
    }
    return nil
}

func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // Always closes file

    // Process file...
    return nil
}

2. Database Transactions

func performTransaction(db *sql.DB) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
            panic(r)  // Re-panic after rollback
        }
    }()

    // Perform transaction operations...
    if err := tx.Commit(); err != nil {
        tx.Rollback()
        return err
    }
    return nil
}

3. HTTP Response Handling

func handleRequest(w http.ResponseWriter, r *http.Request) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Handler panic: %v", r)
            http.Error(w, "Internal server error", http.StatusInternalServerError)
        }
    }()

    // Handle request...
}

Common Patterns

1. Cleanup Pattern

type Resource struct {
    name string
}

func (r *Resource) Close() error {
    fmt.Printf("Cleaning up resource: %s\n", r.name)
    return nil
}

func useResource(name string) error {
    r := &Resource{name: name}
    defer r.Close()

    // Use resource...
    return nil
}

2. Mutex Locking

type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

3. Time Tracking

func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    log.Printf("%s took %s", name, elapsed)
}

func longOperation() {
    defer timeTrack(time.Now(), "longOperation")
    
    // Perform operation...
    time.Sleep(time.Second)
}

Performance Considerations

1. Defer Overhead

// Expensive: Defer in tight loop
func badExample() {
    for i := 0; i < 1000000; i++ {
        f, _ := os.Open("file.txt")
        defer f.Close()  // Defers pile up
    }
}

// Better: Close explicitly in loop
func goodExample() {
    for i := 0; i < 1000000; i++ {
        f, _ := os.Open("file.txt")
        // Process file
        f.Close()
    }
}

2. Panic Cost

// Expensive: Using panic for control flow
func findIndex(slice []int, val int) int {
    for i, v := range slice {
        if v == val {
            panic(i)  // Don't do this
        }
    }
    return -1
}

// Better: Return value directly
func findIndexBetter(slice []int, val int) int {
    for i, v := range slice {
        if v == val {
            return i
        }
    }
    return -1
}

Common Mistakes

1. Defer in Wrong Scope

// Wrong: Deferred close after error check
func wrongScope(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // Never executed if error

    return nil
}

// Right: Defer immediately after successful resource acquisition
func rightScope(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // Will always execute

    return nil
}

2. Recover Outside Deferred Function

// Wrong: Recover outside defer
func wrongRecover() {
    if r := recover(); r != nil {  // Won't work
        fmt.Println("Recovered:", r)
    }
    panic("help!")
}

// Right: Recover inside deferred function
func rightRecover() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()
    panic("help!")
}

3. Ignoring Defer Errors

// Wrong: Ignoring defer errors
func wrongErrorHandling() error {
    f, err := os.Open("file.txt")
    if err != nil {
        return err
    }
    defer f.Close()  // Error ignored

    return nil
}

// Right: Handling defer errors
func rightErrorHandling() (err error) {
    f, err := os.Open("file.txt")
    if err != nil {
        return err
    }
    defer func() {
        if closeErr := f.Close(); closeErr != nil {
            if err == nil {
                err = closeErr
            }
        }
    }()

    return nil
}

Next Steps

  1. Learn about error handling
  2. Explore goroutines
  3. Study testing
  4. Practice with best practices

Additional Resources