Golang defer keyword - How & When to Use it?


GO

Reviewer: Deepak Prasad

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

 

Antony Shikubu

Antony Shikubu

He is highly skilled software developer with expertise in Python, Golang, and AWS cloud services. Skilled in building scalable solutions, he specializes in Django, Flask, Pandas, and NumPy for web apps and data processing, ensuring robust and maintainable code for diverse projects. You can reach out to him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment