A Guide to Closure Functions in Go
Introduction
A closure is when a function inside another function has access to the variables declared in the outer function. This can be reframed as a function “closes over” the variables in its outer scope. Closures are a powerful feature of Go, and they are used in many common Go patterns. In this article, we’ll explore the syntax of closures, how they work, and how to use them.
Prehumble
Before we dive into closures, we need to understand the concept of scope and the lifetime of variables. In Go, variables are declared in a block, and they are only accessible within that block; these variables are termed block-scoped variables. A block-scoped variable has a lifetime that is limited to the block in which it is declared. There are other types of variables in Go, such as package scoped variables and global variables, but we won’t be covering those in this article. To go in-depth check out this article on scope and lifetimes in Go.
A variable can have a longer lifetime than the block which is declared when a pointer to that variable is returned from the block or when a function utilizing the variable is returned; this is called a closure.
Closures use the concept of anonymous functions and nested functions.
Syntax
The syntax for closure is as follows:
The outer function returns a closure. The returned closure has access to the variable x
declared in the outer function.
Let’s look at a more concrete example:
In this example, the function fibonacci
returns a closure. The closure calculates the next number in the Fibonacci sequence and has access to the variables x
and y
declared in the outer function fibonacci
. The closure is returned and assigned to the variable f
. The closure is called in a for
loop, and the result is printed to the console. As long as the f
closure exists, the variables x
and y
will exist. That is why the loop continues incrementing the Fibonacci sequence. If this didn’t happen, the variables x
and y
would be garbage collected and the loop would return a 1
for every iteration.
It’s important to note that the variables in a closure are pointers to the variables in the outer scope and not copies of the variables. This means that if the variable in the outer scope is changed, the variable in the closure will also be changed. Here is an example of this:
By running this program you get the following output:
From this output you can see that the value of i
is always 4
and the address of i
is always the same. As mentioned before, the variables in the closure are pointers to the variables in the outer scope. In the first iteration the value is 0
, the second increments it to 1
, and so on, and when the loop is finished the final value is 4
. Since we are not calling the closure in the loop, we will always get the final value of i
which is 4, and the address of
i` will always be the same.
To fix this we can use the variable j
in the loop to create a new variable for each iteration. This will copy the value of i
in each iteration and will have a different address. Here is the fixed code:
Now when we run the program we get the following output:
By doing this i
gets a new address and value for each iteration of the loop. This is referred to as closure capturing.
Common Use Cases
Many common patterns in Go use closures. Here are a few examples:
Event Handlers
An event handler is a function called when an event occurs. Here is an example of an event handler:
In this example, we have a function middleware
that takes an http.Handler
and returns an http.Handler
. The returned http.Handler
is a closure that prints “Before” before calling the next
http.Handler
and prints “After” after calling the next
http.Handler
. The http.Handler
is then passed to the http.Handle
function and the server is started. This is a common pattern in Go for creating event handlers.
Callbacks
Closures are often used as callbacks in Golang. A callback is a function passed as an argument to another function and is called when that function has completed its task. Here is an example of a callback:
In this example, we define a filter
function that takes a slice of integers and a predicate
function as arguments. The predicate
function is a closure that takes an integer and returns a boolean indicating whether the integer satisfies some condition. The filter
function uses the predicate
function to filter out elements from the slice that don’t meet the condition. We then call the filter
function with a slice of numbers and an anonymous function that checks if a number is even and print the resulting slice.
These are but a few examples of how closures are used in Go. There are many more examples of closures in Go, and you will see them in many of the Go standard library packages.
Conclusion
In this article, we learned about closures in Go. We learned what closure is, how to create a closure, and how to use closures. We also learned about some common use cases for closures in Go.