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.

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.

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.

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 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.

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.

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes three chapters from the book, and over 2.5 hours of screencasts.

You will also receive notifications when I release new articles, along with other freebies that I only share with my mailing list.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who also teaches about Go, web development, algorithms, and anything programming related. He also consults for other companies who have development needs. (If you need some development work done, get in touch!)

Jon is a co-founder of EasyPost, a shipping API that many fortune 500 companies use to power their shipping infrastructure, and prior to founding EasyPost he worked at google as a software engineer.

More in this series

This post is part of the series, Closures in Go.

Spread the word

Did you find this page helpful? Let others know about it!

Vote on Hacker News

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Tags About Me Go Courses

©2018 Jonathan Calhoun. All rights reserved.