Introduction to golang defer keyword
The defer keyword in Go is a statement used to defer execution of a function until the surrounding function returns. In other words , the defer statement postpones the execution of a function or statement until the end of the function calling the function. The defer statement is invoked when the surrounding function executes a return statement, reaches the end of its function body or because the surrounding goroutine is panicking.
Since defer will always get executed when the surrounding function returns, it is a good place to attach cleanup code such as:
- Closing open files
- Releasing network resources
- Closing the Go channel
- Committing database transactions
- And so on
Syntax:
defer fmt.Println("Hello Word")
The expression(e.g fmt.Println(“Hello World”)
) that is being deferred must be a function or a method. Whenever the defer statement is executed, the function value and the parameters to the call are evaluated as usual and saved a new but the actual function is not invoked.
Example.
package main
import "fmt"
func main() {
defer fmt.Println("Hello Word")
fmt.Println("Introduction to defer statement in Go")
}
Explanation.
In the above example, we have two print statements, the first one with a defer
keyword and the other without. The expected behavior would be the first print statement printing the string “Hello world”
in the terminal. But this is not the case when using the defer statement. The compiler will go to defer fmt.Println(“Hello World”)
and postpone its execution and move to the next line of code and execute fmt.Println(“Introduction to the defer statement in Go”)
then execute fmt.Println(“Hello World”)
.
Output
$ go run main.go
Introduction to defer statement in Go
Hello Word
Using multiple defer statements
Go allows for multiple defer statements to be executed in the same program. The order of execution will be last in last out(LIFO).
Example
package main
import "fmt"
func main() {
defer fmt.Println("FOUR")
defer fmt.Println("THREE")
defer fmt.Println("TWO")
defer fmt.Println("ONE")
}
Output
$ go run main.go
ONE
TWO
THREE
FOUR
Using defer keyword with functions and methods
defer
statements can also be used to defer custom functions and methods. When deferring a function or a method , place the defer statement before the custom function or method.
Example.
package main
import (
"fmt"
"math"
)
type circle struct {
radius float64
}
func (c circle) area() {
fmt.Printf("The area is : %f \n", math.Pi*c.radius*c.radius)
}
func (c circle) circumference() {
fmt.Printf("The circumference is %f \n", math.Pi*(c.radius*2))
}
func show() {
fmt.Println("Calculating the area and circumference of a circle")
}
func main() {
c := circle{radius: 7}
defer c.area()
defer c.circumference()
defer show()
}
Explanation
In the above example, we define two methods from the circle struct
namely area()
and circumference()
for calculating area and circumference respectively. We also define a show()
function that prints in the terminal some strings. In the main function, we defer execution of area()
, circumference()
and show()
, and the execution order obeys the last in first out order.
Output
$ go run main.go
Calculating the area and circumference of a circle
The circumference is 43.982297
The area is : 153.938040
Using defer inside loop (Best Practices)
The defer
statement delays a call’s execution until the surrounding function returns. It’s mainly used to reduce boilerplate code. For example, if a resource has to be closed eventually, we can use defer to avoid repeating the closure calls before every single return. However, one common mistake is to be unaware of the consequences of using defer inside a loop.
Let’s look into this problem.
func readFiles(ch <-chan string) error {
// Iterates over the channel
for path := range ch {
// Opens the file
file, err := os.Open(path)
if err != nil {
return err
}
// Defers the call to file.Close()
defer file.Close()
// Do something with file
}
return nil
}
There is a significant problem with this implementation. We have to recall that defer schedules a function call when the surrounding function returns. In this case, the defer calls are executed not during each loop iteration but when the readFiles
function returns. If readFiles
doesn’t return, the file descriptors will be kept open forever, causing leaks.
What are the options to fix this problem? One might be to get rid of defer and handle the file closure manually. But if we did that, we would have to abandon a convenient feature of the Go toolset just because we were in a loop. So, what are the options if we want to keep using defer? We have to create another surrounding function around defer that is called during each iteration.
For example, we can implement a readFile
function holding the logic for each new file path received:
func readFiles(ch <-chan string) error {
for path := range ch {
// Calls the readFile function that contains the main logic
if err := readFile(path); err != nil {
return err
}
}
return nil
}
func readFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
// Keeps the call to defer
defer file.Close()
// Do something with file
return nil
}
In this implementation, the defer function is called when readFile
returns, meaning at the end of each iteration. Therefore, we do not keep file descriptors open until the parent readFiles
function returns.
Another approach could be to make the readFile
function a closure:
func readFiles(ch <-chan string) error {
for path := range ch {
err := func() error {
// ...
defer file.Close()
// ...
// Runs the provided closure
}()
if err != nil {
return err
}
}
return nil
}
Common use cases of defer keyword in golang
1. To Close file
When working with file handling in Go, it's important to close the files that you are working with. Go defer statement becomes a handy tool during opening and closing of files. It ensures that a file is closed and resources released back to the main thread. In the next example we are using the defer statement to close a file after creating.
Example
package main
import (
"fmt"
"log"
"os"
)
func createFile(filename string) {
file, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
fmt.Printf("File with name %s created successfully \n", filename)
defer file.Close()
defer fmt.Printf("Check root folder for %s \n", filename)
}
func main() {
filename := "sample.txt"
createFile(filename)
}
Output
$ go run main.go
File with name sample.txt created successfully
Check root folder for sample.txt
2. To Close HTTP connection
Defer statement can be used with the standard http
library to close connections after making a HTTP request. This is so because the client must close the response body when finished connecting.
Example
package main
import (
"fmt"
"io"
"log"
"net/http"
)
func main() {
res, err := http.Get("https://go.dev")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
d, err := io.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(d))
}
Explanation
In the preceding example, we create a HTTP get request. It is always required that the client must close the response body when finished with it. The defer statement has been used to perform the closing of the body.
Output.
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#00add8">
<link rel="stylesheet" href="/assets/vendor/googleapis/css?family=Material+Icons">
<link rel="stylesheet" href="/css/styles.css">
3. With Waitgroups
golang defer
keyword is commonly used when working with Go waitGroups to postpone the waitgroup.Done()
method.
Example.
package main
import (
"fmt"
"sync"
"time"
)
func myRoutine(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 3; i++ {
fmt.Println(i)
time.Sleep(time.Second)
}
}
func main() {
fmt.Println("Defer with Waitgroups")
var wg sync.WaitGroup
wg.Add(2)
go myRoutine(&wg)
go myRoutine(&wg)
wg.Wait()
}
Explanation
In the preceding example, we define a goroutine that takes in sync.WaitGroup
as a parameter. The goroutine , defers wg.Done()
, meaning the expensive operation(The for loop), will iterate then execute wg.Done()
when the iteration is over. WiatGroups synchronizes goroutines successfully by using the wg.Done()
method.
Output.
$ go run main.go
Defer with Waitgroups
0
0
1
1
2
2
Summary
defer
statement in Go , is a very important and helpful feature. It is largely used for cleanup purposes in the code such as closing connection to the database and HTTP connection and closing files after opening the. It is also largely used for synchronization in Go waitGroups. This article explores these features and use cases of the defer statement.
References
https://go.dev/tour/flowcontrol/12