Introduction
The defer
keyword in Go is used to postpone the execution of a function until the surrounding function returns. deferred functions are often used to ensure that certain cleanup tasks, such as closing files or releasing resources, are performed regardless of how the function exits, whether normally or due to a panic. In this chapter, you will learn the basics of using the defer
keyword, including how it works, common use cases, and examples.
Basics of defer
When you use the defer
keyword, the deferred function is not executed immediately. Instead, it is pushed onto a stack, and when the surrounding function completes, the deferred functions are executed in last-in, first-out (LIFO) order.
Example: Simple defer
Example:
package main
import "fmt"
func main() {
fmt.Println("Starting")
defer fmt.Println("deferred")
fmt.Println("Ending")
}
Output:
Starting
Ending
deferred
In this example, the defer
statement ensures that "deferred" is printed after "Ending".
Multiple deferred Functions
If you have multiple deferred functions, they are executed in the reverse order of their declaration.
Example: Multiple defers
Example:
package main
import "fmt"
func main() {
defer fmt.Println("First")
defer fmt.Println("Second")
defer fmt.Println("Third")
fmt.Println("Done")
}
Output:
Done
Third
Second
First
Common Use Cases
Resource Management
A common use case for defer
is to ensure that resources are properly released, such as closing files or network connections.
Example: Closing a File
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() // Ensure the file is closed when the function returns
// Perform operations on the file
fmt.Println("File opened successfully")
}
Mutex Unlocking
Another common use case is ensuring that a mutex is unlocked after it has been locked, regardless of how the function exits.
Example: Unlocking a Mutex
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
mu.Lock()
defer mu.Unlock() // Ensure the mutex is unlocked when the function returns
// Perform operations that require the mutex to be locked
fmt.Println("Mutex is locked")
}
deferred Function Arguments
The arguments to a deferred function are evaluated when the defer
statement is executed, not when the deferred function is executed.
Example: deferred Function Arguments
Example:
package main
import "fmt"
func main() {
x := 10
defer fmt.Println("deferred value of x:", x)
x = 20
fmt.Println("Current value of x:", x)
}
Output:
Current value of x: 20
deferred value of x: 10
In this example, the value of x
is captured when the defer
statement is executed, so the deferred function prints the original value of x
.
deferred Function Calls and Named Return Values
When using named return values, the deferred function can modify the return values because the deferred function runs after the surrounding function has set the return values.
Example: deferred Function Modifying Return Value
Example:
package main
import "fmt"
func modifyReturnValue() (result int) {
defer func() {
result++
}()
return 5
}
func main() {
fmt.Println("Modified return value:", modifyReturnValue()) // Output: Modified return value: 6
}
In this example, the deferred function increments the return value after it has been set to 5.
Conclusion
The defer
keyword in Go is used for ensuring that cleanup tasks are performed, even if a function exits early. By understanding how to use defer
effectively, you can write more robust and maintainable code that properly manages resources and handles errors. The key features of defer
include executing functions in LIFO order, capturing function arguments at the time of deferment, and allowing modifications to named return values.