Go Defer
Delaying function execution until function returns
โฐ What is Go Defer?
Defer schedules function calls to run after the surrounding function returns. It's perfect for cleanup tasks like closing files, unlocking mutexes, or releasing resources, ensuring they execute regardless of how the function exits.
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // Always closes, even if error occurs
// Process file...
data, err := io.ReadAll(file)
return err
}
Behavior:
file.Close() runs automatically when processFile returns
Key Defer Concepts
LIFO Order
Last deferred function runs first
defer fmt.Println("1")
defer fmt.Println("2") // Runs first
Argument Evaluation
Arguments evaluated when defer is called
x := 5
defer fmt.Println(x) // Prints 5, not later value
Cleanup
Perfect for resource cleanup
defer file.Close()
defer mutex.Unlock()
Panic Safety
Runs even during panics
defer recover()
panic("error")
๐น Basic Defer Usage
Understanding how defer works with execution order:
func demonstrateDefer() {
fmt.Println("Start of function")
defer fmt.Println("Deferred 1")
defer fmt.Println("Deferred 2")
defer fmt.Println("Deferred 3")
fmt.Println("Middle of function")
if true {
defer fmt.Println("Deferred 4 (in if block)")
fmt.Println("Inside if block")
}
fmt.Println("End of function")
}
func main() {
demonstrateDefer()
fmt.Println("After function call")
}
Output:
Start of function
Middle of function
Inside if block
End of function
Deferred 4 (in if block)
Deferred 3
Deferred 2
Deferred 1
After function call
๐น Resource Management
Using defer for proper resource cleanup:
import (
"fmt"
"os"
"sync"
)
func copyFile(src, dst string) error {
// Open source file
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("cannot open source file: %w", err)
}
defer srcFile.Close() // Ensure source file is closed
// Create destination file
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("cannot create destination file: %w", err)
}
defer dstFile.Close() // Ensure destination file is closed
// Copy data
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("copy failed: %w", err)
}
return nil
}
func safeCounter() {
var mu sync.Mutex
var count int
increment := func() {
mu.Lock()
defer mu.Unlock() // Always unlock, even if panic occurs
count++
fmt.Printf("Count: %d\n", count)
// Even if this panics, mutex will be unlocked
if count == 3 {
panic("demonstration panic")
}
}
// Recover from panic to show defer still works
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from: %v\n", r)
fmt.Printf("Final count: %d\n", count)
}
}()
for i := 0; i < 5; i++ {
increment()
}
}
func main() {
safeCounter()
}
Output:
Count: 1
Count: 2
Count: 3
Recovered from: demonstration panic
Final count: 3
๐น Defer with Function Arguments
Understanding how defer evaluates arguments:
func argumentEvaluation() {
x := 1
// Arguments are evaluated immediately
defer fmt.Printf("Immediate evaluation: x = %d\n", x)
// Use closure to capture variable by reference
defer func() {
fmt.Printf("Closure evaluation: x = %d\n", x)
}()
x = 2
fmt.Printf("During function: x = %d\n", x)
x = 3
}
func namedReturns() (result string) {
defer func() {
// Can modify named return values
result = "modified by defer: " + result
}()
return "original value"
}
func deferWithLoop() {
fmt.Println("Defer in loop - common mistake:")
// This creates 5 separate deferred calls
for i := 0; i < 5; i++ {
defer fmt.Printf("Loop defer %d\n", i)
}
fmt.Println("Loop completed")
}
func main() {
fmt.Println("=== Argument Evaluation ===")
argumentEvaluation()
fmt.Println("\n=== Named Returns ===")
result := namedReturns()
fmt.Printf("Returned: %s\n", result)
fmt.Println("\n=== Defer in Loop ===")
deferWithLoop()
}
Output:
=== Argument Evaluation ===
During function: x = 2
Closure evaluation: x = 3
Immediate evaluation: x = 1
=== Named Returns ===
Returned: modified by defer: original value
=== Defer in Loop ===
Loop completed
Loop defer 4
Loop defer 3
Loop defer 2
Loop defer 1
Loop defer 0
๐น Common Defer Patterns
Practical patterns for using defer effectively:
// Pattern 1: Timing functions
func timeFunction(name string) func() {
start := time.Now()
fmt.Printf("Starting %s\n", name)
return func() {
fmt.Printf("%s took %v\n", name, time.Since(start))
}
}
func expensiveOperation() {
defer timeFunction("expensiveOperation")()
// Simulate work
time.Sleep(100 * time.Millisecond)
fmt.Println("Doing expensive work...")
}
// Pattern 2: State restoration
func temporaryChange() {
originalValue := globalConfig.Debug
globalConfig.Debug = true
defer func() {
globalConfig.Debug = originalValue
}()
fmt.Printf("Debug mode: %t\n", globalConfig.Debug)
// Do debug work...
}
// Pattern 3: Error handling with cleanup
func processWithCleanup() error {
resource := acquireResource()
defer func() {
if err := resource.Release(); err != nil {
fmt.Printf("Warning: failed to release resource: %v\n", err)
}
}()
if err := resource.Process(); err != nil {
return fmt.Errorf("processing failed: %w", err)
}
return nil
}
// Pattern 4: Panic recovery with logging
func safeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Operation panicked: %v\n", r)
// Log stack trace, send alerts, etc.
}
}()
// Potentially dangerous operation
riskyWork()
}
type Config struct {
Debug bool
}
var globalConfig = &Config{Debug: false}
type Resource struct{}
func acquireResource() *Resource { return &Resource{} }
func (r *Resource) Process() error { return nil }
func (r *Resource) Release() error { return nil }
func riskyWork() { /* some risky operation */ }
func main() {
fmt.Println("=== Timing Pattern ===")
expensiveOperation()
fmt.Println("\n=== State Restoration ===")
fmt.Printf("Before: Debug = %t\n", globalConfig.Debug)
temporaryChange()
fmt.Printf("After: Debug = %t\n", globalConfig.Debug)
}
Output:
=== Timing Pattern ===
Starting expensiveOperation
Doing expensive work...
expensiveOperation took 100ms
=== State Restoration ===
Before: Debug = false
Debug mode: true
After: Debug = false
๐น Defer Best Practices
Guidelines for using defer effectively:
Do:
- Use for cleanup: Close files, unlock mutexes, release resources
- Place near acquisition: Put defer right after acquiring resource
- Use closures for variables: When you need current variable values
- Handle errors in defer: Check and log cleanup errors
Don't:
- Defer in loops: Creates many deferred calls (use separate function)
- Ignore defer errors: Always handle cleanup errors appropriately
- Rely on execution order: Between different functions
- Use for performance-critical paths: Defer has small overhead
// Good: Defer right after acquisition
func goodFileHandling(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // Right after opening
// Use file...
return nil
}
// Bad: Defer in loop
func badLoopDefer(files []string) {
for _, filename := range files {
file, _ := os.Open(filename)
defer file.Close() // Creates many deferred calls!
// Process file...
}
}
// Good: Separate function for loop
func goodLoopDefer(files []string) {
for _, filename := range files {
processFile(filename)
}
}
func processFile(filename string) {
file, err := os.Open(filename)
if err != nil {
return
}
defer file.Close() // Only one defer per file
// Process file...
}