The use of defer in Go

In my previous blog post I have covered how to setup your development environment for Golang including a simple hello world. In case this is your first Go project please have a look on this blog post first and then come back to learn about the use of defer in Go.

Defer will always be triggered at the end of a function. So even if the code panics in some location of the executing code it will guarantee the deferred code will be executed. A panic in Go is an unhandled error and causes the program execution to get halted. After a panic a defer will be executed. Panic is not recommended to use it for exception handling. It is better to handle exceptions using Golang error object. However for showcasing the use of defer I will use it in this blogpost, please don’t take that as a best practice.

So lets take a look at an example:

main.goplayground
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
deferExample()
fmt.Println("Returned from deferExample.")
}

func deferExample() {
defer fmt.Println("Deferred log.")
fmt.Println("Print line.")
}

Above code will result in the following output.

1
2
3
4
go run main.go
Print line.
Deferred log.
Returned from deferExample.

As you can see the deferred code will be executed at the end of the deferExample function just before the function returns. Now lets look at an example where the code panics. So lets add following functions to main.go and call it in your main function.

main.goplayground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func deferPanicExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from panic", r)
}
}()
fmt.Println("Call recurse.")
recursePanic(0)
fmt.Println("Return normally from recurse.")
}

func recursePanic(count int) {
if count > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", count))
}
defer fmt.Println("Defer log in recurse", count)
fmt.Println("Print line in recurse", count)
recursePanic(count + 1)
}

Above code is a recursive function which we will have panic as soon count is bigger then 3. fmt.Sprintf is a way to format a string based on a value. See fmt documentation for more information on the verbs (%v). This is required as the panic function paramter should be a string.

Another interesting topic is the recover() function which allows you to recover from a panic down the call stack. As we are doing this recover in a self invoking anonymous deferred function call we are sure this will always be executed during a call to the deferPanicExample. So lets have a look at the new output of our program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go run main.go
Print line.
Deferred log.
Returned from deferExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Panicking!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Recover from panic 4
Returned from deferPanicExample.

As you notice the Println with message Return normally from recurse. never happens as the function panics and therefore never reaches this line of code at the end of the function. Another thing to notice is that the defer in the recursive methods is only executed once the recursive call stack exits and will do that in reverse order of the call stack. Recursion basically works in a LIFO matter (Last in first out). Then the deferred anonymous function will trigger as the defer keyword guarantees to make this happen even if there is a panic. As we provided the count to panic you can see the log message contains the count at the moment the program panicked.

So now lets see how we can rewrite this function a bit to have it return normally without a panic to see how the flow will look like now.

main.goplayground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func deferRecurseExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from panic", r)
}
}()
fmt.Println("Call recurse.")
recursePanic(0)
fmt.Println("Return normally from recurse.")
}

func recurse(count int) {
if count > 3 {
fmt.Println("Stopping recursion!")
return
}
defer fmt.Println("Defer log in recurse", count)
fmt.Println("Print line in recurse", count)
recurse(count + 1)
}

Now you will notice the Println of Return normally from recurse. does happen. You also notice the recover() call results in nil as there was no panic, so nothing to recover from, and therefore not print the Recover from panic log.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
go run main.go
Print line.
Deferred log.
Returned from deferExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Panicking!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Recover from panic 4
Returned from deferPanicExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Stopping recursion!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Return normally from recurse.
Returned from deferRecurseExample.

Practical usecase

Now we know about the internal workings of defer we can have a look at a more practical usecase.

So depending on your background you might know about the Closable interface in Java or the IDisposable interface in c#. In general those interfaces are implemented on classes like database connections, file readers etc. To free up memory after using those classes you generally call the close() or dispose() functions to free up those resources, so the garbage collector can free the memory. Not doing that is basically introducing memory leaks. In c# you do this using a try finally block or the shorthand using statement to guarantee this code is called no matter if an exception occurred or not.

In Go we just learned we can use defer to make sure a piece of code is invoked with a 100% guarantee. So lets have a look on a small example reading X amount of bytes from a file.

main.goplayground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"os"
)

func main() {
readBytes(10)
}

func readBytes(amount int) {
pwd, _ := os.Getwd()
f, _ := os.Open(pwd + "/awesome.txt")

defer fmt.Println("File closed")
defer f.Close()
defer fmt.Println("Going to close file")

bytes := make([]byte, amount)
count, _ := f.Read(bytes)

fmt.Printf("%d bytes: '%s'\n", count, string(bytes))
}

This program will print the string from our text file up to the amount of 10 bytes. Again if you want to have more details on the verbs for string formatting have a look in the fmt package documentation.

1
2
3
4
5
echo "I am super sexy." > awesome.txt
go run main.go
10 bytes: I am super
Going to close file
File closed

As you can see I create a file in the same folder with the text I am super sexy.. In general I don’t want everyone to know I’m sexy ;-), so now you also know why I made the program only read the first 10 bytes. It would be anyhow enough to share with the world that I am super.

So in general whenever any operation (Read, Peek etc.) on the file fails, in the end it will be guaranteed to that the file handle will be closed. Also note that multiple defer statements in the same function are executed in reverse order as you define them.

Thanks for reading this blog post. Also have a look at this blog on golang.org to get more details on defer panic and recover in Go.

Share