Skip to main content
โšก Calmops

Defer, Panic, and Recover

Defer, Panic, and Recover

Defer, panic, and recover are Go’s mechanisms for cleanup, error signaling, and exception-like handling. Understanding these is crucial for robust Go programs.

Defer Statement

Basic Defer

package main

import "fmt"

func main() {
    defer fmt.Println("Deferred 1")
    defer fmt.Println("Deferred 2")
    defer fmt.Println("Deferred 3")

    fmt.Println("Main")
}

// Output:
// Main
// Deferred 3
// Deferred 2
// Deferred 1

Defer with Functions

package main

import "fmt"

func cleanup() {
    fmt.Println("Cleaning up...")
}

func process() {
    defer cleanup()
    fmt.Println("Processing...")
}

func main() {
    process()
}

// Output:
// Processing...
// Cleaning up...

Resource Cleanup

File Handling

package main

import (
    "fmt"
    "os"
)

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close()

    // Read file...
    fmt.Println("File opened and will be closed automatically")
}

func main() {
    readFile("data.txt")
}

Database Connections

package main

import "fmt"

type Database struct {
    connected bool
}

func (db *Database) Connect() error {
    db.connected = true
    fmt.Println("Connected to database")
    return nil
}

func (db *Database) Close() error {
    db.connected = false
    fmt.Println("Disconnected from database")
    return nil
}

func queryDatabase() {
    db := &Database{}
    if err := db.Connect(); err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer db.Close()

    // Query database...
    fmt.Println("Querying database")
}

func main() {
    queryDatabase()
}

Defer with Arguments

Argument Evaluation

package main

import "fmt"

func main() {
    x := 10
    defer fmt.Println("x is", x)  // x evaluated now

    x = 20
    fmt.Println("x is", x)
}

// Output:
// x is 20
// x is 10

Defer with Named Return Values

package main

import "fmt"

func divide(a, b int) (result int, err error) {
    defer func() {
        fmt.Printf("Dividing %d by %d, result: %d, error: %v\n", a, b, result, err)
    }()

    if b == 0 {
        err = fmt.Errorf("division by zero")
        return
    }

    result = a / b
    return
}

func main() {
    divide(10, 2)
    divide(10, 0)
}

Panic

Triggering Panic

package main

import "fmt"

func main() {
    fmt.Println("Start")
    panic("Something went wrong!")
    fmt.Println("End")  // Never reached
}

// Output:
// Start
// panic: Something went wrong!

Panic with Conditions

package main

import "fmt"

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

func main() {
    fmt.Println(divide(10, 2))  // 5
    fmt.Println(divide(10, 0))  // Panic!
}

Recover

Basic Recover

package main

import "fmt"

func safeDivide(a, b int) (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
            result = 0
        }
    }()

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

func main() {
    fmt.Println(safeDivide(10, 2))  // 5
    fmt.Println(safeDivide(10, 0))  // 0 (recovered)
}

Recover with Error Handling

package main

import (
    "fmt"
    "log"
)

func processData(data []int) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Panic recovered: %v", r)
        }
    }()

    for i := 0; i < 10; i++ {
        fmt.Println(data[i])  // Panic if index out of bounds
    }
}

func main() {
    data := []int{1, 2, 3}
    processData(data)
    fmt.Println("Program continues")
}

Defer Patterns

Multiple Defers

package main

import "fmt"

func transaction() {
    fmt.Println("Begin transaction")

    defer func() {
        fmt.Println("Rollback")
    }()

    defer func() {
        fmt.Println("Commit")
    }()

    fmt.Println("Execute queries")
}

func main() {
    transaction()
}

// Output:
// Begin transaction
// Execute queries
// Commit
// Rollback

Defer with Closures

package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func(n int) {
            fmt.Println("Deferred:", n)
        }(i)
    }
}

// Output:
// Deferred: 2
// Deferred: 1
// Deferred: 0

Best Practices

โœ… Good: Use Defer for Cleanup

// DO: Use defer for resource cleanup
func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // Process file
    return nil
}

โŒ Bad: Manual Cleanup

// DON'T: Forget cleanup in error paths
func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }

    // Process file
    file.Close()  // Might not execute if error occurs
    return nil
}

โœ… Good: Recover Only When Necessary

// DO: Use recover for exceptional cases
defer func() {
    if r := recover(); r != nil {
        log.Printf("Panic: %v", r)
    }
}()

โŒ Bad: Overuse Panic/Recover

// DON'T: Use panic for normal error handling
if err != nil {
    panic(err)  // Use error returns instead
}

โœ… Good: Document Panic Behavior

// DO: Document when functions panic
// Divide panics if divisor is zero
func Divide(a, b int) int {
    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

Summary

Defer, panic, and recover provide:

  1. Automatic cleanup with defer
  2. Exception-like behavior with panic/recover
  3. Guaranteed execution of deferred functions
  4. LIFO ordering for multiple defers
  5. Resource safety patterns

These features enable robust error handling and resource management in Go.

Comments