Defer

Welcome to tutorial no. 29 in Golang tutorial series.

What is Defer?

Defer statement is used to execute a function call just before the surrounding function where the defer statement is present returns. The definition might seem complex but it’s pretty simple to understand by means of an example.

Example

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func totalTime(start time.Time) {
 9	fmt.Printf("Total time taken %f seconds", time.Since(start).Seconds())
10}
11
12func test() {
13	start := time.Now()
14	defer totalTime(start)
15	time.Sleep(2 * time.Second)
16	fmt.Println("Sleep complete")
17}
18
19func main() {
20	test()
21}

Run in playground

The above is a simple program which illustrates the use of defer. In the above program, defer is used to find out the total time taken for the execution of the test() function. The start time of the test() function execution is passed as argument to defer totalTime(start) in line no. 14. This defer call is executed just before test() returns. totalTime prints the difference between start and the current time using time.Since in line no. 9. To simulate some computation happening in test(), a 2 second sleep is added in line no. 15.

Running this program will print

Sleep complete
Total time taken 2.000000 seconds

The output correlates to the 2 second sleep added. Before the test() function returns, totalTime is called and it prints the total time taken for test() to execute.

Arguments evaluation

The arguments of a deferred function are evaluated when the defer statement is executed and not when the actual function call is done.

Let’s understand this by means of an example.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func displayValue(a int) {
 8	fmt.Println("value of a in deferred function", a)
 9}
10func main() {
11	a := 5
12	defer displayValue(a)
13	a = 10
14	fmt.Println("value of a before deferred function call", a)
15}

Run in playground

In the program above a initially has a value of 5 in line no. 11. When the defer statement is executed in line no. 12, the value of a is 5 and hence this will be the argument to the displayValue function which is deferred. We change the value of a to 10 in line no. 13. The next line prints the value of a. This program outputs,

value of a before deferred function call 10
value of a in deferred function 5

From the above output it can be understood that although the value of a changes to 10 after the defer statement is executed, the actual deferred function call displayValue(a) still prints 5.

Deferred methods

Defer is not restricted only to functions. It is perfectly legal to defer a method call too. Let’s write a small program to test this.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type person struct {
 8	firstName string
 9	lastName string
10}
11
12func (p person) fullName() {
13	fmt.Printf("%s %s",p.firstName,p.lastName)
14}
15
16func main() {
17	p := person {
18		firstName: "John",
19		lastName: "Smith",
20	}
21	defer p.fullName()
22	fmt.Printf("Welcome ")	
23}

Run in playground

In the above program we have deferred a method call in line no. 21. The rest of the program is self explanatory. This program outputs,

Welcome John Smith

Multiple defer calls are placed in stack

When a function has multiple defer calls, they are pushed to a stack and executed in Last In First Out (LIFO) order.

We will write a small program which prints a string in reverse using a stack of defers.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func main() {
 8	str := "Gopher"
 9	fmt.Printf("Original String: %s\n", string(str))
10	fmt.Printf("Reversed String: ")
11	for _, v := range str {
12		defer fmt.Printf("%c", v)
13	}
14}

Run in playground

In the program above, the for range loop in line no. 11, iterates the string and calls defer fmt.Printf("%c", v) in line no. 12. These deferred calls will be added to a stack.

stack of defers

The above image represents the content of the stack after the defer calls are added. The stack is a last in first out datastructure. The defer call that is pushed to the stack last will be popped out and executed first. In this case defer fmt.Printf("%c", 'n') will be executed first and hence the string will be printed in reverse order.

This program will print

Original String: Gopher
Reversed String: rehpoG

Practical use of defer

In this section we will look into some more practical uses of defer.

Defer is used in places where a function call should be executed irrespective of the code flow. Let’s understand this with the example of a program which makes use of WaitGroup. We will first write the program without using defer and then we will modify it to use defer and understand how useful defer is.

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8type rect struct {
 9	length int
10	width  int
11}
12
13func (r rect) area(wg *sync.WaitGroup) {
14	if r.length < 0 {
15		fmt.Printf("rect %v's length should be greater than zero\n", r)
16		wg.Done()
17		return
18	}
19	if r.width < 0 {
20		fmt.Printf("rect %v's width should be greater than zero\n", r)
21		wg.Done()
22		return
23	}
24	area := r.length * r.width
25	fmt.Printf("rect %v's area %d\n", r, area)
26	wg.Done()
27}
28
29func main() {
30	var wg sync.WaitGroup
31	r1 := rect{-67, 89}
32	r2 := rect{5, -67}
33	r3 := rect{8, 9}
34	rects := []rect{r1, r2, r3}
35	for _, v := range rects {
36		wg.Add(1)
37		go v.area(&wg)
38	}
39	wg.Wait()
40	fmt.Println("All go routines finished executing")
41}

Run in playground

In the program above, we have created a rect struct in line no. 8 and a method area on rect in line no. 13 which calculates the area of the rectangle. This method checks whether the length and width of the rectangle is less than zero. If it is so, it prints a corresponding error message else it prints the area of the rectangle.

The main function creates 3 variables r1, r2 and r3 of type rect. They are then added to the rects slice in line no. 34. This slice is then iterated using a for range loop and the area method is called as a concurrent Goroutine in line no. 37. The WaitGroup wg is used to ensure that the main function is waiting until all Goroutines finish executing. This WaitGroup is passed to the area method as an argument and the area method calls wg.Done() in line nos. 16, 21 and 26 to notify the main function that the Goroutine is done with its job. If you notice closely, you can see that these calls happen just before the area method returns. wg.Done() should be called before the method returns irrespective of the path the code flow takes and hence these calls can be effectively replaced by a single defer call.

Let’s rewrite the above program using defer.

In the program below, we have removed the 3 wg.Done() calls in the above program and replaced it with a single defer wg.Done() call in line no. 14. This makes the code more simple and readable.

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8type rect struct {
 9	length int
10	width  int
11}
12
13func (r rect) area(wg *sync.WaitGroup) {
14	defer wg.Done()
15	if r.length < 0 {
16		fmt.Printf("rect %v's length should be greater than zero\n", r)
17		return
18	}
19	if r.width < 0 {
20		fmt.Printf("rect %v's width should be greater than zero\n", r)
21		return
22	}
23	area := r.length * r.width
24	fmt.Printf("rect %v's area %d\n", r, area)
25}
26
27func main() {
28	var wg sync.WaitGroup
29	r1 := rect{-67, 89}
30	r2 := rect{5, -67}
31	r3 := rect{8, 9}
32	rects := []rect{r1, r2, r3}
33	for _, v := range rects {
34		wg.Add(1)
35		go v.area(&wg)
36	}
37	wg.Wait()
38	fmt.Println("All go routines finished executing")
39}

Run in playground

This program outputs,

rect {8 9}'s area 72
rect {-67 89}'s length should be greater than zero
rect {5 -67}'s width should be greater than zero
All go routines finished executing

There is one more advantage of using defer in the above program. Let’s say we add another return path to the area method using a new if condition. If the call to wg.Done() was not deferred, we have to be careful and ensure that we call wg.Done() in this new return path. But since the call to wg.Done() is defered, we need not worry about adding new return paths to this method.

This brings us to the end of this tutorial. I hope you liked it. Please leave your feedback and comments. Please consider sharing this tutorial on twitter and LinkedIn. Have a good day.

Next tutorial - Error Handling