Skip to main content

🗓️ 02112024 2256
📎

GO DEFER

Core Concept: Defer executes a function when the surrounding function returns, regardless of how it returns.

Why It Matters

Guarantees cleanup (files, connections, locks) without try-finally blocks. Prevents resource leaks.

When to Use

Use for:

  • Cleanup of resources
  • Unlocking mutexes
  • Closing files/connections

Don't use for:

  • Long-running operations
  • Loops (defers accumulate)

vs Java try-with-resources

Java: try-with-resources auto-closes
Go: defer + Close() - explicit but guaranteed

WARNING

Defer in loop accumulates until function ends. Wrap in func() for per-iteration cleanup. ```

Trade-offs

Pros: Guaranteed cleanup, clear intent
Cons: Delayed execution, loop gotcha

Defer is commonly used with go_error_handling for cleanup before returns.

Quick Reference

// Basic usage
file, err := os.Open("data.txt")
if err != nil {
return err
}
defer file.Close() // Executes when function returns

// Multiple defers (LIFO order)
defer fmt.Println("First")
defer fmt.Println("Second")
defer fmt.Println("Third")
// Prints: Third, Second, First

// Loop pattern
for _, f := range files {
func() {
f, _ := os.Open(f)
defer f.Close() // Closes each iteration
}()
}
PatternUse Case
defer file.Close()Close files
defer conn.Close()Close connections
defer mu.Unlock()Release locks
defer resp.Body.Close()HTTP responses

Examples

EXAMPLE

Database transaction with rollback:

func TransferMoney(from, to int, amount float64) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback() // Safe to call even after Commit

if err := debit(tx, from, amount); err != nil {
return err // Rollback happens automatically
}

if err := credit(tx, to, amount); err != nil {
return err // Rollback happens automatically
}

return tx.Commit() // On success, commit wins
}

Mutex unlock pattern:

type SafeCounter struct {
mu sync.Mutex
count int
}

func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock() // Guaranteed unlock even if panic

c.count++
// Complex logic here
// Unlock happens regardless of return path
}

HTTP response cleanup:

func FetchData(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close() // Always close body

return io.ReadAll(resp.Body)
}

```

References