My Go courses are discounted for the next few weeks to help out anyone who may need or want access to them. I'm also going to try to help out anyone who can't afford a course, and I will be writing posts about working from home over the next week in an attempt to help anyone new to WFH. Read more here .

Gotchas and Common Mistakes with Closures in Go

This article is part of a series This article is part of the series Closures in Go. You may want to check out earlier articles before reading this one. You can also find links to the next and previous articles at the top of the page (if they are published).

defer and go statements take function calls as arguments

This is a pretty simple mistake, and it is one that the compiler will sometimes catch for you, but not always. When you use defer or go in golang you need to pass a function call as the argument, not just a function declaration.

If you are creating your closure inline it is hard to make this mistake because the compiler will catch it.

func main () { defer func () { fmt . Prinltn ( "teardown" ) } }

This code will result in a compilation error expression in go/defer must be function call , so it is pretty hard to not catch, but let’s say you have a function that sets up your application server and returns a function that should be run right before your application exits in order to handle closing the database connection or tearing down anything else that needs torn down. That function might look something like this.

func setup () func () { fmt . Println ( "pretend to set things up" ) return func () { fmt . Println ( "pretend to tear things down" ) } }

Now lets say we are using this in our main() function to setup our server, and we want to defer the returned function. This is where the mistake slips in, and you might need to examine the code closely to catch it.

func main () { defer setup () } func setup () func () { fmt . Println ( "pretend to set things up" ) return func () { fmt . Println ( "pretend to tear things down" ) } }

What do you expect the output to be?

Go ahead and run the code. You can do so on the Go Playground right here. You will get the following output.

pretend to set things up

What went wrong? Why don’t we see the output pretend to tear things down ?

It turns out both defer and go take a function call as an argument, not a function. This is important because it means that our code is actually defering the call to setup() , and it is never actually running the returned function.

Instead what we really want is the following.

package main import "fmt" func main () { defer setup ()() } func setup () func () { fmt . Println ( "pretend to set things up" ) return func () { fmt . Println ( "pretend to tear things down" ) } }

It is also worth noting that this setup/teardown strategy is another great way to utilize closures in your code!

See the difference? It is really subtle - we need a second () after our call to setup() because we are telling our program to defer calling the function returned from setup() .

A clearer way to show this might instead be:

f := setup () defer f ()

Variables declared in for loops are passed by reference

When you declare a new variable inside of a for loop, it is important to remember that the variables aren’t being redeclared with each iteration. Instead the variable is the same, but instead the value that is stored in the variable is being updated.

Let’s look at an example of how this can cause issues with your closures.

package main import "fmt" func main () { var functions [] func () for i := 0 ; i < 10 ; i ++ { functions = append ( functions , func () { fmt . Println ( i ) }) } for _ , f := range functions { f () } }

What do you expect this to output? Go ahead and run it on the Go Playground.

The output you will get is:

10 10 10 10 10 10 10 10 10 10

The issue we are experiencing here is that i is declared inside of a for loop, and it is being changed with each iteration of the for loop. When we finally call all of our functions in the functions slice they are all referencing the same i variable which was set to 10 in the last iteration of the for loop.

The same thing can happen if you use ranges. Here is a similar example, but it uses the keyword range .

package main import "fmt" func main () { ints := [] int { 1 , 2 , 3 , 4 } var functions [] func () for _ , val := range ints { functions = append ( functions , func () { fmt . Println ( val ) }) } for _ , f := range functions { f () } }

In this example our output will be:

4 4 4 4

This is caused by the same issue as before. Instead of using the value of val in each closure, we are referencing the variable which is being changed with each iteration of the loop.

So, how do we fix it? One way is to utilize the fact that function parameters in Go are passed by value. This means that if we called the function doStuff(i) inside of a for loop that it would pass the value of i into the function as a parameter at that specific time, and not a reference to the i variable.

Here is an example of this in action:

package main import "fmt" func main () { var functions [] func () for i := 0 ; i < 10 ; i ++ { functions = append ( functions , build ( i )) } for _ , f := range functions { f () } } func build ( val int ) func () { return func () { fmt . Println ( val ) } }

What if we don’t want to create the build() function globally?

Unfortunately, this example required us to create the build() function globally. If we had to do this for every closure we wanted to create, our code might get pretty crowded quickly. Especially if we only wanted to create a really basic closure.

Luckily we can still solve this issue by using an anonymous function, but be careful with this approach as it can quickly become hard to read, understand, and maintain if it gets too big.

Here is the same example as before, but it uses an anonymous function to create the closure.

package main import "fmt" func main () { var functions [] func () for i := 0 ; i < 10 ; i ++ { functions = append ( functions , func ( val int ) func () { return func () { fmt . Println ( val ) } }( i )) } for _ , f := range functions { f () } }

Let’s take a moment to walk through what is happening here.

First we have declare a function inline that takes in an integer value and returns a function.

func ( val int ) func () { return func () { fmt . Println ( val ) } }

This is just like any other anonymous function, except we don’t assign it to a variable. Instead we immediately call the function with i as the parameter being passed in. This is the (i) part right after the function declaration.

After the anonymous function is called it returns a func() , which is then appended to the functions slice with the line functions = append(functions, ...) .

If that still seems confusing, lets look at another example using the same anonymous function, but this time we will assign it to a variable.

package main import "fmt" func main () { var functions [] func () fn := func ( val int ) func () { return func () { fmt . Println ( val ) } } for i := 0 ; i < 10 ; i ++ { functions = append ( functions , fn ( i )) } for _ , f := range functions { f () } }

Notice how we aren’t adding fn to the functions slice, but we are passing in the return value of it which is a func() .

It was also pointed out on Reddit that you can also solve this problem by creating a new variable and assigning it with the value of i . Below is an example of this approach.

package main import "fmt" func main () { var functions [] func () for i := 0 ; i < 10 ; i ++ { j := i functions = append ( functions , func () { fmt . Println ( j ) }) } for _ , f := range functions { f () } }

You could even use i as your new variable (as weird as that may seem at first), so your code could instead read i := i instead of j := i . This is called shadowing a variable, and can lead to some confusing bugs if abused.

That’s all folks

Hopefully after seeing these mistakes in action you will be able to catch them if they creep into your own code.

Unfortunately it was hard to demonstrate these examples without using code that is a little less beginner-friendly, so if you found anything hard to grasp don’t hesitate to email me - jon@calhoun.io - and I’ll be happy to try to clear things up!

This article is part of the series, Closures in Go. Previous Article