Blog.

The use of defer in Go

MF

Marco Franssen /

7 min read1305 words

Cover Image for 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:

package main

import "fmt"

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

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

playground

Above code will result in the following output.

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.

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)
}

playground

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.

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.

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)
}

playground

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.

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.

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))
}

playground

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.

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.

You have disabled cookies. To leave me a comment please allow cookies at functionality level.

More Stories

Cover Image for Test and benchmark your code in go

Test and benchmark your code in go

MF

Marco Franssen /

When I started writing my first programs in Go, I noticed the tooling ships out of the box with test and benchmark features. You simply follow some naming conventions when it comes to file names. You import a reference to the "testing" package which is kind of part of the language. Aaaand… Ready set, and of you Go with writing some tests and benchmarks in Go. In my earlier blog post I briefly touched writing a test in Go already. I recommend reading this blogpost whenever you are a real newby wi…

Cover Image for Concurrency in Go

Concurrency in Go

MF

Marco Franssen /

A reason to choose Go over other programming languages could be to have the need for building software which requires concurrency. Go is built with concurrency in mind. You can achieve concurrency in Go by using Go routines. A Go routine is a lightweidght thread to explain this in easy words for the people with c# and Java backgrounds. Please experienced Gophers don't take my words litterly as I do know a Go routine shouldn't be compared to threads like this, but at least it is the easiest for m…

Cover Image for Start on your first Golang project

Start on your first Golang project

MF

Marco Franssen /

A couple of months ago I started to do some coding in Go a.k.a Golang. Not only because they have an awesome logo ;-). My main reason was because I wanted to have something running as bare metal as possible on my Raspberry Pi and I wanted to have it available for different platforms to be easy to install. Some other reasons are the ease of creating async code by using Go in front of your methods and the unique approach of channels to sync between go routines (threads). I have been reading a lot…

Cover Image for How to add network driver to Windows 10 PE

How to add network driver to Windows 10 PE

MF

Marco Franssen /

Very recently I have been trying to reinstall my Laptop using my WinPE approach as it didn't have a optical drive anymore. However my problem was that the WinPE image I created was lacking the network driver for my laptop. So then I recreated a WinPE USB drive using the Windows 10 ADK, hoping it would include the required driver. However unlucky me I still had no network when I booted by new laptop using my new Windows 10 PE USB drive. Therefore I had to add the network driver for my laptop to t…