What is a Closure?

In a previous article on testing with Go there is an example that uses a closure to create table-driven tests. This is a fairly common practice in Go, but after writing the article a developer reached out to me and made the following comment.

“I learned something. Mainly that I have no idea how Go closures work”

Ouch! That means that despite the rest of the article being packed with great content on testing, if a reader didn’t understand closures they weren’t going to get the full benefit of the article.

This post is intended to fix that problem. In it I we are going to discuss what closures are and why you should care about them. Then in a followup article, 5 Useful Ways to Use Closures in Go (redundant, I know), we will explore some of the more common use cases for closures to help give you ideas of where and when to use closures in your own code. Each use case has a specific example that I have encountered, so they aren’t just imaginary uses.

Anonymous functions

Before we jump into closures, we need to first talk about anonymous functions. An anonymous function is the same as a regular old function, but it doesn’t have a name - hence the term “anonymous”. Instead, an anonymous function is created dynamically, much like a variable is.

Like most things, this can be easier to explain with code. Below is what a normal function would look like.

func DoStuff() {
  // Do stuff
}

If we wanted to turn this same function into an anonymous function, we wouldn’t declare it quite the same way; rather than starting with the func keyword, we could instead create a variable that has a func() type. After doing this, we could create and assign an anonymous function to he variable.

var DoStuff func() = func() {
  // Do stuff
}

There are a lot of subtle differences between these two, but for most practical purposes, the big difference is that we could assign a new function to the DoStuff variable at runtime, allowing us to dynamically change what DoStuff() does.

package main

import "fmt"

var DoStuff func() = func() {
  // Do stuff
}

func main() {
  DoStuff()

  DoStuff = func() {
    fmt.Println("Doing stuff!")
  }
  DoStuff()

  DoStuff = func() {
    fmt.Println("Doing other stuff.")
  }
  DoStuff()
}

If you run this program you would see the following output.

Doing stuff!
Doing other stuff.

We are seeing two different lines of output because we declared three different anonymous functions, and the last two each are printing out something different.

Anonymous functions can accept parameters, return data, and do pretty much anything else a normal function can do. You can even assign a regular function to a variable just like you do with an anonymous function.

package main

import "fmt"

var DoStuff func() = func() {
	// Do stuff
}

func RegFunc() { fmt.Println("reg func") }

func main() {
	DoStuff()
	DoStuff = RegFunc
	DoStuff()
}

The only real difference between a regular function and an anonymous one is that anonymous functions aren’t declared at a package level. They are declared more dynamically and are typically either used and then forgotten or are assigned to a variable for later use.

Closures

A closure is a special type of anonymous function that references variables declared outside of the function itself. That means that like in the previous examples, we will be creating a function dynamically, but in this case we will be using variables that weren’t passed into the function as a parameter, but instead were available when the function was declared.

This is very similar to how a regular function can reference global variables. You aren’t directly passing these variables into the function as a parameter, but the function has access to them when it is called.

Let’s take a look at a closure in action. In this next example we are going to create a closure that keeps track of how many times it has been called, and returns that number.

package main

import "fmt"

func main() {
  n := 0
  counter := func() int {
    n += 1
    return n
  }
  fmt.Println(counter())
  fmt.Println(counter())
}

If you were to run this code you would get the output:

1
2

Notice how our anonymous function has access to the n variable, but this was never passed in as a parameter when counter() was called. This is what makes it a closure!

Closures provide data isolation

One problem with the previous example is a problem that can also pop up when using global variables. Any code inside of the main() function has access to n, so it is possible to increment the counter without actually calling counter(). That isn’t what we want; Instead we would rather have n isolated so that no other code has access to it.

To do this we need to look at another interesting aspect of closures, which is the fact that they can still reference variables that they had access to during creation even if those variables are no longer referenced elsewhere.

package main

import "fmt"

func main() {
  counter := newCounter()
  fmt.Println(counter())
  fmt.Println(counter())
}

func newCounter() func() int {
  n := 0
  return func() int {
    n += 1
    return n
  }
}

In this example our closure references the variable n even after the newCounter() function has finished running. This means that our closure has access to a variable that keeps track of how many times it has been called, but no other code outside of the newCounter() function has access to this variable. This is one of the many benefits of a closure - we can persist data between function calls while also isolating the data from other code.

Up Next…

In my next post - - I go over several common use cases for closures, including ways that closures can make code easier to read and understand, how closures can prevent the callback hell that rears its head so often in javascript, and even how you can use closures to create middleware that times your web applications execution time. You should check it out so you can easily recognize situations where closures can be helpful!

Following that I will have a small another article dedicated to gotchas and common mistakes that developers make when creating closures, and how to avoid them.

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.