Defer Statement in Golang
The defer statement in Go is used to delay the execution of a function until the surrounding function returns. Deferred functions are executed in Last In, First Out (LIFO) order, which makes them perfect for cleanup operations like closing files, releasing resources, or unlocking mutexes.
Basic Defer Usage
package main
import "fmt"
func main() {
fmt.Println("Start")
defer fmt.Println("This will be executed last")
fmt.Println("Middle")
defer fmt.Println("This will be executed second")
fmt.Println("End")
}Output:
Start
Middle
End
This will be executed second
This will be executed lastDefer with Function Calls
Defer works with any function call, not just print statements.
package main
import (
"fmt"
"time"
)
func doSomething() {
fmt.Println("Doing something...")
time.Sleep(100 * time.Millisecond)
}
func cleanup() {
fmt.Println("Cleaning up...")
}
func main() {
defer cleanup() // This will run when main exits
fmt.Println("Starting main")
doSomething()
fmt.Println("Main finished")
}Resource Management
Defer is commonly used for resource cleanup:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
// Defer file close - this will run when main exits
defer file.Close()
// Read from file...
buffer := make([]byte, 100)
n, err := file.Read(buffer)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
// File will be automatically closed here due to defer
}Multiple Defers - LIFO Order
Multiple defer statements execute in reverse order:
package main
import "fmt"
func main() {
fmt.Println("Main started")
defer fmt.Println("Defer 1")
defer fmt.Println("Defer 2")
defer fmt.Println("Defer 3")
fmt.Println("Main finished")
}Output:
Main started
Main finished
Defer 3
Defer 2
Defer 1Defer with Parameters
Deferred functions capture the current value of variables at the time defer is executed, not when the function is called:
package main
import "fmt"
func main() {
i := 0
defer fmt.Println("Deferred value:", i) // Captures i=0
i = 10
fmt.Println("Current value:", i) // Prints 10
}Output:
Current value: 10
Deferred value: 0For functions with parameters, the parameters are evaluated when defer is executed:
package main
import "fmt"
func printValue(x int) {
fmt.Println("Value:", x)
}
func main() {
x := 1
defer printValue(x) // x is evaluated here as 1
x = 2
fmt.Println("x is now:", x)
}Defer in Loops
Be careful with defer in loops - each iteration creates a new deferred function:
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println("Deferred:", i)
}()
}
fmt.Println("Loop finished")
}Output:
Loop finished
Deferred: 3
Deferred: 3
Deferred: 3To capture the loop variable correctly, pass it as a parameter:
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func(n int) {
fmt.Println("Deferred:", n)
}(i) // Pass i as parameter
}
fmt.Println("Loop finished")
}Output:
Loop finished
Deferred: 2
Deferred: 1
Deferred: 0Defer with Methods
Defer works with methods too:
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock() // Unlock when function returns
c.value++
fmt.Println("Incremented to:", c.value)
}
func main() {
counter := &Counter{}
counter.Increment()
}Panic and Recover with Defer
Defer is commonly used with panic and recover for error handling:
package main
import "fmt"
func safeDivision(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("division by zero: %v", r)
}
}()
if b == 0 {
panic("division by zero")
}
result = a / b
return
}
func main() {
result, err := safeDivision(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}Defer Performance Considerations
Defer has a small performance cost. In performance-critical code, you might want to avoid defer:
package main
import (
"fmt"
"time"
)
func withDefer() {
start := time.Now()
for i := 0; i < 1000000; i++ {
func() {
defer func() {}() // Empty defer
}()
}
fmt.Printf("With defer: %v\n", time.Since(start))
}
func withoutDefer() {
start := time.Now()
for i := 0; i < 1000000; i++ {
func() {
// No defer
}()
}
fmt.Printf("Without defer: %v\n", time.Since(start))
}
func main() {
withDefer()
withoutDefer()
}Common Patterns
File Operations
package main
import (
"bufio"
"fmt"
"os"
)
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // Ensure file is closed
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
return scanner.Err()
}Database Transactions
package main
import (
"database/sql"
"fmt"
)
func updateUser(db *sql.DB, userID int, newName string) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback() // Rollback if not committed
_, err = tx.Exec("UPDATE users SET name = ? WHERE id = ?", newName, userID)
if err != nil {
return err
}
// If we reach here, commit the transaction
return tx.Commit()
}Mutex Locking
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
value int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock() // Ensure unlock happens
c.value++
fmt.Println("Value:", c.value)
}HTTP Response Cleanup
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
// Simulate some processing
fmt.Fprintln(w, "Hello, World!")
// In a real handler, you might defer cleanup operations
defer func() {
// Cleanup code here
fmt.Println("Request cleanup")
}()
}Best Practices
- Use defer for cleanup - Always use defer for resource cleanup
- Keep defer simple - Deferred functions should be simple and not complex
- Be aware of evaluation timing - Remember that defer captures values at defer time
- Use defer in loops carefully - Pass loop variables as parameters if needed
- Defer order matters - Multiple defers execute in reverse order
- Don’t ignore defer performance - Avoid defer in hot loops if performance is critical
- Use defer with recover - Combine defer with recover for error handling
External Resources:
Related Tutorials: