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)
 6
 7func finished() {
 8	fmt.Println("Finished finding largest")
 9}
10
11func largest(nums []int) {
12	defer finished()	
13	fmt.Println("Started finding largest")
14	max := nums[0]
15	for _, v := range nums {
16		if v > max {
17			max = v
18		}
19	}
20	fmt.Println("Largest number in", nums, "is", max)
21}
22
23func main() {
24	nums := []int{78, 109, 2, 563, 300}
25	largest(nums)
26}

Run in playground

The above is a simple program to find the largest number of a given slice. The largest functions takes a int slice as parameter and prints the largest number of this slice. The first line of the largest function contains the statement defer finished(). This means that the finished() function will be called just before the largest function returns. Run this program and you can see the following output printed.

Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest

The largest function starts executing and prints the first two lines of the above output. And before it could return, our deferred function finished executes and prints the text Finished finding largest :)

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
 7
 8type person struct {
 9	firstName string
10	lastName string
11}
12
13func (p person) fullName() {
14	fmt.Printf("%s %s",p.firstName,p.lastName)
15}
16
17func main() {
18	p := person {
19		firstName: "John",
20		lastName: "Smith",
21	}
22	defer p.fullName()
23	fmt.Printf("Welcome ")	
24}

Run in playground

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

Welcome John Smith

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 printA(a int) {
 8	fmt.Println("value of a in deferred function", a)
 9}
10func main() {
11	a := 5
12	defer printA(a)
13	a = 10
14	fmt.Println("value of a before deferred function call", a)
15
16}

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 printA 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 printA(a) still prints 5.

Stack of defers

When a function has multiple defer calls, they are pushed on 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	name := "Naveen"
 9	fmt.Printf("Original String: %s\n", string(name))
10	fmt.Printf("Reversed String: ")
11	for _, v := range name {
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 pulled 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 output,

Original String: Naveen
Reversed String: neevaN

Practical use of defer

The code samples we saw so far don’t show the practical use of defer. In this section we will look into some 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 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 blocked 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 understandable.

 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