How to defer in Golang like a pro
The defer keyword in Go offers a way to ensure that resources are properly released and that certain actions are performed, no matter how a function exits.
The easiest way to learn about it is to look at lots of code, so lets do that!
How defer Works
When you use defer before a function call, Go schedules that function to run after the surrounding function returns. Deferred functions are pushed onto a stack and executed in last-in-first-out (LIFO) order.
Basic Example
package main
import "fmt"
func main() {
fmt.Println("Function start")
defer fmt.Println("First deferred")
defer fmt.Println("Second deferred")
fmt.Println("Function end")
}
Outputs:
Function start
Function end
Second deferred
First deferred
In this example, “Second deferred” is printed before “First deferred” because deferred calls are executed in reverse order of their deferment.
Practical Applications
Resource Cleanup
One of the most common uses of defer is to ensure that resources like files or network connections are properly closed.
package main
import (
"bufio"
"fmt"
"os"
)
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // Ensures the file is closed when the function exits
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
By deferring file.Close()
, you guarantee that the file is closed regardless of how the function exits, even if an error occurs during file processing.
Mutex Locks
In concurrent programming, defer is useful for unlocking mutexes.
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
func updateSharedResource() {
mu.Lock()
defer mu.Unlock() // Ensures the mutex is unlocked when the function exits
// Critical section
fmt.Println("Updating shared resource")
}
This approach prevents deadlocks by making sure that the mutex is always unlocked.
Timing Function Execution
You can measure the execution time of a function using defer along with time tracking.
package main
import (
"fmt"
"time"
)
func longRunningFunction() {
start := time.Now()
defer func() {
elapsed := time.Since(start)
fmt.Println("Function took", elapsed)
}()
// Simulate long operation
time.Sleep(2 * time.Second)
}
func main() {
longRunningFunction()
}
Outputs:
Function took 2.000123456s
Important Details to Know About Defer
Evaluation of Deferred Function Arguments
Arguments to deferred functions are evaluated immediately, but the function call is executed later.
package main
import "fmt"
func main() {
x := 10
defer fmt.Println("Deferred:", x)
x = 20
fmt.Println("Current:", x)
}
Outputs:
Current: 20
Deferred: 10
Even though x
is updated to 20, the deferred function captures the value of x
at the time defer is called.
Modifying Return Values
Deferred functions can modify named return values in the surrounding function.
package main
import "fmt"
func compute() (result int) {
defer func() {
result *= 2
}()
result = 5
return
}
func main() {
fmt.Println("Result:", compute())
}
Outputs:
Result: 10
The deferred function modifies the result
after it has been set but before it is returned.
Error Handling and Recovery
Using defer with recover(), you can handle panics gracefully.
package main
import "fmt"
func mayPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
panic("An unexpected error occurred")
}
func main() {
mayPanic()
fmt.Println("Program continues")
}
Outputs:
Recovered from: An unexpected error occurred
Program continues
The deferred function checks if a panic has occurred and handles it, allowing the program to continue running.
Best Practices for defer in Go
- Avoid Deferring in Loops: Deferring inside loops can lead to performance issues and resource leaks.
for i := 0; i < 1000; i++ {
defer fmt.Println(i) // Not recommended
}
- Use Anonymous Functions for Complex Logic: When you need more control, wrap your deferred calls in anonymous functions.
defer func() {
// Complex deferred logic
}()
- Be Mindful of Variable Scope: Since deferred functions execute after the surrounding function returns, ensure that variables used are accessible.
Wrapping Up
We hope you found this blog post on defer useful! If you did, consider joining our mailing list below, or checking out some more of our blog posts here: