What do you need to know about Golang’s Defer?

Kelvin Benzali
CodeX
Published in
13 min readAug 8, 2021

Golang is a functional programming language. It is a powerful and flexible language programming that offers the developer the full scope of designing their program architecture from scratch with a high degree of freedom. The language is unique in its own way. Therefore, there are sets of unique features Golang has that other languages might not have. One of them is the Deferred function.

What is Go’s Defer?

Go’s defer is a scheduled function call that will be run at the end of the function life. Simply put, it is a clean-up function call. You can set up a named function or construct an anonymous function explicitly shown as in the example below. It is a useful feature for clean-up tasks that may include closing connections, unsubscribing events, resource clean-up, and even recover from panic.

func main() {
defer foo()
defer func() {
fmt.Println("hello")
}()
}
func foo() {
fmt.Println("foo")
}

Defer mechanism itself is simple and yet it is very powerful when used properly. When defer statement is called, it pushes the function call into a stack list and uses the LIFO principle. Hence, if a function states multiple defer, the last pushed defer will be called first as shown in the example below.

for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}

Output:

4 3 2 1 0

There are a lot of other things about Go’s defer mechanics such as the three basic rules about defer and work together with recover and panic. A more detailed golang mechanism can be found on the golang blog. You can also read more about deferring in general on Golang documentation

Useful Tips

In this article, we will explore and discuss a lot of things that defer can do and cannot do, what you need to do or avoid doing as a developer, defer limitation, and capabilities. Keep in mind that these cases are mostly experienced personally by me. Any suggestions written here are purely based on my opinion. Therefore, it might not help answer your question and problem directly but I hope that sharing my research and experiences on these tips might be helpful about understanding defer in more depth.

Nil function

When you try to defer a variable function. Always ensure that the variable function is not nil. Otherwise, it will panic on defer execution. You can play around on Golang playground.

var cleanUp func()func main() {
defer cleanUp()
fmt.Println("hello")
}

Although, it is bad practice and not recommended for using function as a variable. Try to avoid using a variable for function as much as possible and consider another approach like private function. Not only it ensures a non-nil function scenario, but it also enhances the good practice of the separation of concern design principle in Golang.

func main() {
defer cleanUp()
fmt.Println("hello")
}
func cleanUp() {
fmt.Println("foo")
}

Watch out for the order

As stated on the blog Golang, one of the basic rules of defer is the following:

Deferred function calls are executed in Last In First Out order after the surrounding function returns.

This rule simply stated that deferred function only executed on function returns in LIFO order. Always keep that rule in mind when using defer function. There are several common mistakes related to this rule that you might take notes of.

Use for loop with caution

Be careful when using defer function inside loop unless you know what you are doing. Remember that for loop is not a function and defer might not work as you expected as shown in the example below.

func main() {
for i := 0; i < 3; i++ {
var num int
defer func() {
fmt.Println("Defer", num)
}()
num = Double(i)
}
}
func Double(num int) int {
result := num * 2
fmt.Println("Inner", result)
return result
}

Output:

Inner 0
Inner 2
Inner 4
Defer 4
Defer 2
Defer 0

In this example, defer functions don't get executed until the end of the main function. This can imply a serious consequence on crucial execution such as closing connection in deferring inside a loop. Furthermore, the order of the defer function call is inverted because of the LIFO stack principle.

We can solve this problem with several approaches.

1.Create a function inside the loop that encapsulates each loop execution as a whole. It ensures that the defer function is always executed for every loop.

for i := 0; i < 3; i++ {
func() {
var num int
defer func() {
fmt.Println("Defer", num)
}()
num = Double(i)
}()
}

2.Just call it directly on the spot without using defer at all. This is the most simple way to solve it. It might not have to do anything with defer, but this might be the best practice to use for loop in Golang.

for i := 0; i < 3; i++ {
num := Double(i)
fmt.Println("Defer", num)
}

LIFO order might be tricky

Although defer execution is straightforward once you understand the LIFO stack principle. It still might be confusing to makes sense of the code if you place them randomly. Therefore, defer function is generally written right after the original function that needs some clean-up for their own purposes. Here is an example of a bad code that utilizes deferring functions.

You might realize a few problems with this example.

  • First, db.Close()does not execute if db.Begin()got an error that will produces a resource leak.
  • Second, err variable in here is used all over the place and overwrote inside other deferred functions as well which might confuse the next defer function that uses err variable for validation.

Here is an example of good practice code using deferring functions.

As a developer, you might realize which code is easier to read and makes sense immediately. It might seem trivial to you, but it will help a lot of people and reduce human error.

Know the right order first before using defer

Keep your awareness of the function you are going to use. Read the function documentation and dive down to the code directly if needed. Make sure you are confident about what the function will return on every case. After then, you can know in which order the deferred function needs to be executed. Here is an example of a possible wrong order scenario.

rows, err := tx.Query("SELECT ...")
defer rows.Close()
if err != nil {
return err
}

Output when err is non-nil and rows is nil:

panic: runtime error: invalid memory address or nil pointer dereference

Defer param scope is tricky

My param hates scope

Golang variable scope is straightforward. A variable defined on the outer scope is accessible to every inner scope. However, once the inner scope defines the new variable under the same name. It will create a new variable on the inner scope referencing a whole new variable different from that on the outer scope.

This becomes more complicated with the entrance of named return values. In this case, the variable is instantiated immediately at the start of function life and can be used as a regular variable. Uniquely, you can use return without using any local variable and it will use the current value of the named variable of the function.

The complexity of named return values is the root cause of some of the common mistakes on Golang. One of them is when using the deferred function. An example is shown below.

func execute() (err error){
db, err := sql.Open()
if err != nil {
log.Fatal(err)
}
defer func(){
if err := db.Close(); err != nil {
...
}
}
...
return
}

We expect that once db.Close() got an error, the function will return an error from closing db. But, that is not the case in this code. Since the db.Close() is inside a deferred function and it also instantiates a new err variable with := which does not overwrite the error from the named return (err error). The solution is simply to use the named return variable directly in the deferred function since the function returning values with err.

defer func(){
if err = db.Close(); err != nil {
...
}
}

Evaluated on the run

The second rule of Go’s defer based on blog Golang is

A deferred function’s arguments are evaluated when the defer statement is evaluated.

Maybe, in this case, an example will speak on its own more effectively than when I try to explain it. Playground

func a() {
i := 0
defer fmt.Println(i)
i++
fmt.Println(i)
return
}

Output:

1
0

In summary, anything that passed into the deferred function as a parameter will be evaluated immediately. Then the deferred function will use the evaluated parameter even though the original parameter might already have a different value right now. Don’t mix this with a deferred anonymous function that is using the variable directly without any parameter. In that case, it points on the same variable. Usually, developers oversight this type of detail and lead them to one of the most common mistakes in defer.

Suppose we have a wallet type. Playground

Output:

$ 1000
$ 500
$ 500

How about we try to pass myWallet as a parameter. Playground

func main() {
myWallet := Wallet{1000}
myWallet.MyMoney()
myWallet.AddMoney(100)
defer func(myWallet Wallet) {
myWallet.MyMoney()
}(myWallet)
myWallet.DeductMoney(600)
defer myWallet.MyMoney()
}

Output:

$ 1000
$ 500
$ 1100

Then try to change myWallet into a pointer myWallet := &Wallet{1000} . What do you think it’s going to happen? The answer can be found later section under Defer method receiver. Keep reading!

For loop again…

As stated before in section Use loop with caution, using defer in a loop can become quite tricky. Now by utilizing evaluated param and param scope, defer in a loop is very troublesome. For example when you want to print an index loop in an anonymous function. Playground

for i := 0; i < 2; i++ {
defer func() {
fmt.Println(i)
}()
fmt.Println(i)
}

Output:

0
1
2
2

What happened is the deferred function parameter points the same variable state after the loop is finished. If the deferred function wants to use the variable state on each loop then the deferred function needs to evaluate the param at each loop. There are multiple ways to achieve this. It can be done by using an anonymous function with parameter.

for i := 0; i < 2; i++ {
defer func(i int) {
fmt.Println(i)
}(i)
fmt.Println(i)
}

Or by using defer function directly:

for i := 0; i < 2; i++ {
defer fmt.Println(i)
fmt.Println(i)
}

Output:

0
1
1
0

The forgotten return

Did you know that the Go’s defer does not return values nor error? Even if the deferred function returns something, it does not affect anything. However, the deferred function can assign and change the return named values as stated in blog Golang as the third rule.

Deferred functions may read and assign to the returning function’s named return values.

Where is the error?

Often developers use defer right away because it is so convenient, they forgot the most important thing in the Golang language. It is error handling. Unlike other programming languages where you can catch the error on parent functions, Golang relies on error handling from returned values. Usually, some IDE such as Visual Basic and GoLand provides a feature to warn developers about the unhandled error. But, as a good Golang developer, we need to practice the habit of handling errors better without always relying on warning signs.

defer db.Close()

Better error handling:

defer func () {
if err := db.Close(); err != nil {
log.Println(err)
}
}()

Missing return

If Go’s defer does not handle the returned values from the deferred function, then where are the values end up in the code? It’s gone, reduce to atoms, figuratively. It simply does not pick up or referred to any variable and is eventually collected by the garbage collector.

Then the question comes up on how are the defer return values on its parent function if needed? One way to solve this problem is by using named return values as stated in the previous section. The defer functions just need to assign the value into the named return parameters that will be used by the parent function as the final return value.

func execute(tx *sql.Tx) (err error){
defer func () {
if err != nil {
if errRb := tx.Rollback(); errRb != nil {
err = errRb
return
}
}
}()

return
}

However, keep in mind that the purpose of Go’s defer in the first place is to encourage clean code on functional programming. Hence, you do not need to use defer on every occasion when the function is still able to return the value normally. Defer is there to help you on a clean-up task that is essential to be run after every return of the function. But, it is not a tool for returning values that are not essential. For example like tx.Commit().

Defer method receiver

Until this point, we have discussed mostly defer attributes and mechanisms. Now, we can start to spice things up with pointer and method receiver. Golang method can also be used together with defer and compliment each other but there is a twist. We are going to use the same example used in the Evaluated on the run section. Let’s change the pointer of the method instead. Playground

func (wallet Wallet) MyMoney() {
fmt.Println("$", wallet.money)
}
...func main() {
myWallet := Wallet{1000}
myWallet.AddMoney(100)
defer myWallet.MyMoney()
myWallet.DeductMoney(600)
defer myWallet.MyMoney()
}

Output:

$ 500
$ 1100

Compared when MyMoney is using the pointer on method receiver:

func (wallet *Wallet) MyMoney() {
fmt.Println("$", wallet.money)
}

Output:

$ 500
$ 500

What happened here? Method without pointer prints the result value with the state of the value when registered/evaluated on deferred function. On the other hand, the method with pointer prints both results that are reflected on the last updated value.

It has something to do with how the defer works by evaluating the param immediately. Refer back to the Evaluated on the run section for more detail. When using defer, the receiver will be evaluated immediately and copied to the defer for later execution. If the receiver (Wallet) does not have a pointer, then the state of Wallet and its money copied into the receiver is from when it’s registered at that time (in this case 1100).

It applies to the pointer method receiver (*Wallet) as well. But, in this case, the receiver copied into the defer is the copy of the pointer that still pointing to the same address as the current receiver. Therefore, the value of the money in defer receiver still follows all the changes happening in the main function until the end.

Using anonymous function

The trick does not end here. There is more to it. As we know from the example from Evaluated on the run, that we use the myWallet to be evaluated immediately with anonymous function func(){}(). Playground

defer func(myWallet Wallet) {
myWallet.MyMoney()
}(myWallet)

Try to play with it. You will encounter a new question. Why the result is always the same even though I have changed the method receiver pointer? It is because myWallet is evaluated immediately on the spot as deferred function param. It means that the second instance of myWallet variable is created for the deferred function. Hence, any changes on the original myWallet would not change the second variable of myWallet because it has a different memory address. If we can represent it on normal execution, it would be like this:

func main() {
myWallet := Wallet{1000}
myWallet.AddMoney(100)
myWallet2 := myWallet
// Do something deferredFunc(myWallet2)
}
func deferredFunc(myWallet Wallet) {
myWallet.MyMoney()
}

If you are still confused, you can play around with your IDE debugger to see each memory address on Wallet inside MyMoney function. Git gist.

Pointer-ception

Suppose you have a complex usecase of using method receiver pointer on a pointer variable. Yes, it is actually confusing at first, but you will get used to it. Let’s see an example first.

func (wallet *Wallet) MyMoney() {
fmt.Println("$", wallet.money)
}
func main() {
myWallet := &Wallet{1000}
myWallet.AddMoney(100)
defer myWallet.MyMoney()
myWallet.DeductMoney(600)
defer myWallet.MyMoney()
}

Output:

$ 500
$ 500

Let’s experiment it with normal method receiver.

func (wallet Wallet) MyMoney() {
fmt.Println("$", wallet.money)
}
func main() {
myWallet := &Wallet{1000}
myWallet.AddMoney(100)
defer myWallet.MyMoney()
myWallet.DeductMoney(600)
defer myWallet.MyMoney()
}

Output:

$ 500
$ 1100

The result shows the answers. Just like we have discussed before when a receiver of a function is not a pointer, then the value receiver copied is only the state of a struct. Since the receiver is not a pointer, the value is already fixed on defer registration regardless of myWallet is a pointer or not.

Here is a table to summarize this section of defer with method receiver.

Recovery on the rescue

Golang has its own way to produce an unexpected runtime error that usually shouldn’t occur under normal circumstances. It is called panic. As you have seen, Golang treats error as a value in which needs to be returned. Unlike other languages that have their own handling error as an exception and catch it. Therefore, panic can be troublesome to handle since it can stop the program if not handled properly. One such way that we can handle panic on golang is using the recover built-in function.

Recover function purpose is to regain control from panic errors. In addition to handling panic, it also captures the value given from the panic and returned it as a response value. You can see the read more about recover on blog golang.

Unfortunately, many developers are not aware of recover function based on my observation. It might have to do with the nature recover needs to be placed since usually it is written inside a wrapper or the top most parent function like main. It is one of the reasons why we don’t see recover function a lot inside a program. Hence, I will try my best to explain how to use recover properly.

Recover and defer is a couple

Yes, the title means it. Recover is totally useless without a defer. They need to be together to work as intended. They are meant to be together. Let’s try to separate them and see what will happens. (I feel bad for them)

func main() {
recover()
panic(“panicking”)
}

Output: The panic is still roaming wild.

panic: panicking

Here is an example of how to use recover properly.

func main() {
defer func() {
recoveredValue := recover()
fmt.Println("recovered value:", recoveredValue)
}()
panic("panicking")
}

Output:

recovered value: panicking

Recovering values

Now that we know how to use the recover function properly. The next step is to utilize the recovered values from panic. Most of the time, developers initialize panic with a string or an error. But, according to the panic golang documentation, the panic parameter is actually an interface. It also applies to recover function returned value type as well based on documentation.

func panic(v interface{})func recover() interface{}

Based on these documentations, we can technically assign anything useful into panic and recovered later on. Developers can analyze the panic more than before, or maybe use it for other use cases. This allows full flexibility on how we can handle and use panic in our program. Let’s try it out with customized error for example.

Suppose we have a customized generalError. Playground

Output:

recovered: error 500

Conclusion

Golang defer function is certainly useful and essential for writing Go clean code in good practice. Based on what we have seen and discussed, Golang defer is easy to use and understand, yet it is hard to master. After learning Golang defer mechanism and its good practice, we can see defer in a new way and ready to use defer full potential. I hope this article can help you to understand defer in-depth. Thank you for reading my article so far.

--

--

Kelvin Benzali
CodeX
Writer for

Software Engineer at Tokopedia. Technology and history enthusiast.