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 .

5 Useful Ways to Use Closures in Go

In this article we are going to explore several different real world use cases for closures and anonymous functions so that you can get a better understanding of when closures are a good fit, and see how they are applied to different situations.

This article is part of a series This article is part of the series An Introduction to Templates 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).

Without messing around, lets jump right into some of these use cases!

1. Isolating data

The first use case we are going to discuss is isolating data. We covered this briefly in the last article in this series, so we won’t spend too much time on this example.

Lets say you want to create a function that has access to data that persists even after the function exits. For example, you want to count how many times the function has been called, or you want to create a fibonacci number generator, but you don’t want anyone else to have access to that data (so they can’t accidentally change it). You can use closures to achieve this.

package main import "fmt" func main () { gen := makeFibGen () for i := 0 ; i < 10 ; i ++ { fmt . Println ( gen ()) } } func makeFibGen () func () int { f1 := 0 f2 := 1 return func () int { f2 , f1 = ( f1 + f2 ), f2 return f1 } }

Sure, you could use a custom type to create something very similar, but if you wanted to work with multiple number generators you would likely need to eventually declare an interface and accept that as an argument to other functions that use the generator, like so.

type Generator interface { Next () int } func doWork ( g Generator ) { n := g . Next () fmt . Println ( n ) // ... do work with n }

With a closure you can instead just require that a function is passed in as your argument, since you really only care about one of the methods in the Generator interface anyway.

func doWork ( f func () int ) { n := f () fmt . Println ( n ) // ... do work with n }

2. Wrapping functions and creating middleware

Functions in Go are first-class citizens. What this means is that you can not only create anonymous functions dynamically, but you can also pass functions as parameters to a function. For example, when creating a web server it is common to provide a function that processes a web request to a specific route.

package main import ( "fmt" "net/http" ) func main () { http . HandleFunc ( "/hello" , hello ) http . ListenAndServe ( ":3000" , nil ) } func hello ( w http . ResponseWriter , r * http . Request ) { fmt . Fprintln ( w , "<h1>Hello!</h1>" ) }

In this case, the function hello() is passed to the http.HandleFunc() function and is called when that route is matched.

While this code doesn’t require a closure, closures are incredibly helpful if we want to wrap our handlers with more logic. A perfect example of this is when we want to create middleware to do work before or after our handler executes.

What is middleware? Middleware is basically a fancy term for reusable function that can run code both before and after your code designed to handle a web requst. In Go these are typically accomplished with closures, but in different programming languages they may be achieved in other ways. Using middleware is common when writing web applications, and they can be useful for more than just timers (which you will see an example of below). For instance, middleware can be used to write code to verify if a user is logged in once, then apply it to all of your member-only pages.

Let’s look at how a simple timer middleware would work in Go.

package main import ( "fmt" "net/http" "time" ) func main () { http . HandleFunc ( "/hello" , timed ( hello )) http . ListenAndServe ( ":3000" , nil ) } func timed ( f func ( http . ResponseWriter , * http . Request )) func ( http . ResponseWriter , * http . Request ) { return func ( w http . ResponseWriter , r * http . Request ) { start := time . Now () f ( w , r ) end := time . Now () fmt . Println ( "The request took" , end . Sub ( start )) } } func hello ( w http . ResponseWriter , r * http . Request ) { fmt . Fprintln ( w , "<h1>Hello!</h1>" ) }

Notice that our timed() function takes in a function that could be used as a handler function, and returns a function of the same type, but the returned function is different that the one passed it. The closure being returned logs the current time, calls the original function, and finally logs the end time and prints out the duration of the request. All while being agnostic to what is actually happening inside of our handler function.

Now all we need to do to time our handlers is to wrap them in timed(handler) and pass the closure to the http.HandleFunc() function call.

3. Accessing data that typically isn’t available

While this uses a technique that we saw earlier in this article, it is worth pointing to on its own because it is that useful.

A closure can also be used to wrap data inside of a function that otherwise wouldn’t typically have access to that data. For example, if you wanted to provide a handler access to a database without using a global variable you could write code like the following.

package main import ( "fmt" "net/http" ) type Database struct { Url string } func NewDatabase ( url string ) Database { return Database { url } } func main () { db := NewDatabase ( "localhost:5432" ) http . HandleFunc ( "/hello" , hello ( db )) http . ListenAndServe ( ":3000" , nil ) } func hello ( db Database ) func ( http . ResponseWriter , * http . Request ) { return func ( w http . ResponseWriter , r * http . Request ) { fmt . Fprintln ( w , db . Url ) } }

Now we can write handler functions as if they had access to a Database object while still returning a function with the signature that http.HandleFunc() expects. This allows us to bypass the fact that http.HandleFunc() doesn’t permit us passing in custom variables without resorting to global variables or anything of that sort.

4. Binary searching with the sort package

Closure are also often needed to use packages in the standard library, such as the sort package.

This package provides us with tons of helpful functions and code for sorting and searching sorted lists. For example, if you wanted to sort a slice of integers and then search for the number 7 in the slice, you would use the sort package like so.

package main import ( "fmt" "sort" ) func main () { numbers := [] int { 1 , 11 , - 5 , 7 , 2 , 0 , 12 } sort . Ints ( numbers ) fmt . Println ( "Sorted:" , numbers ) index := sort . SearchInts ( numbers , 7 ) fmt . Println ( "7 is at index:" , index ) }

But what happens if you want to search a slice where each element is a custom type? Or if you want to find the index of the first number that is 7 or higher rather than just the first index of 7?

To do this, you would instead use the sort.Search() function, and you need to pass in a closure that can be used to determine if the number at a specific index meets your criteria.

sort.Search is a binary search The sort.Search function does a binary search, so it expects a closure that will return false for any index prior to your criteria being met, and true for any index after it is met. This means you can’t use this to “find the index of 7 in a list”, but instead need to rephrase your logic to be “what is the index of the first number greater than or equal to 7?”, and then check to see if that number is 7 once you get the index back. Related Article Let's Learn Algorithms: An Introduction to Binary Search

Let’s take a look at this in action using the example we described above; We will be searching for the index of the first number in our list that is greater than or equal 7.

package main import ( "fmt" "sort" ) func main () { numbers := [] int { 1 , 11 , - 5 , 8 , 2 , 0 , 12 } sort . Ints ( numbers ) fmt . Println ( "Sorted:" , numbers ) index := sort . Search ( len ( numbers ), func ( i int ) bool { return numbers [ i ] >= 7 }) fmt . Println ( "The first number >= 7 is at index:" , index ) fmt . Println ( "The first number >= 7 is:" , numbers [ index ]) }

In this example our closure is the simple little function passed as the second argument to sort.Search() .

func ( i int ) bool { return numbers [ i ] >= 7 }

This closure accesses the numbers slice even though it is never passed in, and returns true for any number that is greater than or equal to 7. By doing so, it allows sort.Search() to work without needing to have any knowledge about what the underlying data type you are using is, or what criteria you are attempting to meet. It simply needs to know if a value at a specific index meets your criteria.

5. Deferring work

If you are unfamiliar with JavaScript, especially legacy code, this example may be a little hard to follow. Feel free to email me - jon@calhoun.io - if you have any questions, feedback, or if you can think of ways this could be made clearer.

If you have ever used javascript, you have likely run across some code that looks like this:

doWork ( a , b , function ( result ) { // use the result here }); console . log ( "hi!" );

In the javascript example above this is what is known as a callback. What we are essentially doing is telling our program to run the function doWork() with the variables a and b , and then our last argument is the function that we want it to run after that function completes. So when doWork() finishes, it then calls our function with there result of doWork() .

The benefit to this approach is that doWork() can be coded as an asynchronous function - that is we can continue on with our code after calling doWork() and print “hi!” out to the screen BEFORE doWork() has finished running. When doWork() does finish running it already knows what code to run next.

While this example isn’t impossibly hard to follow, having several nested functions can lead to what is commonly referred to as callback hell. Below is an example of this.

doWork1 ( a , b , function ( result ) { doWork2 ( result , function ( result ) { doWork3 ( result , function ( result ) { // use the final result here }); }); }); console . log ( "hi!" );

If you were lucky, you might have seen this done with promises instead of nested functions. Regardless, it is somewhat confusing at first glance, and you probably asked yourself, “what the hell is going on?” the first time you saw code like this.

This is basically the same as the previos javascript example, but we are using three nested callbacks. That means that we want doWork1() to run first, and then doWork2() to run after it completes, and then finally we want doWork3() to run after doWork2() has completed.

While it IS possible to create callbacks like this in Go, goroutines combined with closures make it much easier to write this code in a more readable fashion, and this is shown below.

go func () { result := doWork1 ( a , b ) result = doWork2 ( result ) result = doWork3 ( result ) // Use the final result }() fmt . Println ( "hi!" )

There are two primary benefits to this approach. The first is that it is incredibly clear what you are doing. While the javascript example might not be crystal clear, it is easy to tell that doWork3() runs after doWork1() and doWork2() have finished in the Go code. It is also clear that this will all be happening concurrently because we have a go keyword being used.

The second benefit to this approach is that the author of doWork1() doesn’t have to worry about writing an asynchronous version of his function. If you were to call doWork1() in Go you would expect your entire program to wait for that function to stop running, and then once you get the result it will continue on. If you need that function to operate concurrently you would simply run it in a goroutine likein the previous example.

In javascript, developers instead need to write both an asynchronous and a synchronous version of each method that they want to offer both variants in, which can be tedious and confusing.

Some promise libraries have helped with many of these issues, and changes coming to the language itself are also helping. While this is helpful moving forward, it is unlikely to entirely remedy the problem (at least immediately) because legacy JavaScript code will always exist and developers will still need to maintain it. That is why I personally appreciate that this was baked into Go from the onset - we gophers don’t have to worry about old and new code looking incredibly and feeling incredibly different.

Want to improve your Go skills? Are you looking to practice Go, but can’t think of a good project to work on? Or maybe you just don’t know what techniques and skills you need to learn next, so you don't know where to start. If so, don’t worry - I’ve got you covered! Gophercises is a FREE course where we work on exercise problems that are each designed to teach you a different aspect of Go. This includes topics ranging from basic string manipulation all the way to more advanced topics like functional options and concurrency. Each exercise has a sample solution, as well as a screencast (video) where I code the solution while walking you through the code. Plus, the Gophers are really cute 😉 FREE Course Gophercises - Exercises for Budding Gophers

Summary

While this article clearly isn’t an exhaustive list of use cases for closures, I hope that the ones discussed here will provide you with a clear understanding of their uses. Closures are an incredibly powerful and useful tool that every developer should get comfortable using.

If you are looking for additional examples, you can check out another article I wrote - How to test with Go. In the article there is an example where we create multiple test cases for a single test function, and in the code we use a closure to setup what is known as table-driven testing. This is definitely another very common use case for closures.

In the last article in this series - Gotchas and Common Mistakes with Closures in Go - I cover some of the more common mistakes that can be made with closures, what you would see when you encounter these bugs, and how to prevent/fix them. Most of these are non-obvious, so it is worth a look just to make sure you don’t accidentally code one of the bugs up yourself.