A closure is a function that you can pass around that retains the same “environment” as the one it had when it was created. In other words, the function defined in the closure ‘remembers’ the environment in which it was created. It takes some time to get a hang of closures. At least it took me sometime to get going on closures. Hopefully this post will help you understand the same.
Before getting into what
closures are, lets understand the basics first - functions/ anonymous functions.
1. Anonymous functions
Functions with name are named functions!. Functions which can be created without a name are anonymous functions. That was easy :). As you can see from code snippet, one can create an anon function and call directly or one can even pass around anonymous functions. Generally closures make use of anonymous function, whereas that always need not be the case. In golang, one can create anon functions and pass functions as arguments to functions. Functions are first class.
getPrintMessage creates an anonymous function and returns it.
printfunc gets the
anon function and calls it.
The function foo is an inner function and still has access to variable text, which is outside of
foo but belongs to outer function. This function foo is called a
closure. It is said to
close over the variables in the outer scope. In this case it closes over
foo always holds a reference to the
3. Returning closures and using outside
In this example we see how we can return the closure and use it outside of the function where
it is declared. foo is a closure which is returned to the main function when outer is called.
The actual execution of foo happens in main when it is invoked using (). The code outputs
So the closure foo still has a reference to the variable text even though the outer function has exited.
4. Closures and state
Closures preserve state. What this implies is the the state of variables is contained in a closure
at the time of creation(declaration). What this essentially means is
- the state(variable references) are same per creation of a closure. All closures created together have same state.
- the state are different for different creations of a closure.
Lets digest it with the below gist. Below is a function counter which accepts a start value and returns two closures - counter(ctr) and incrementer(incr). This implies per invocation of counter, the state (
start in this case)
is the same value referenced by both the closures. For another invocation of counter, there would be a different
reference which would be shared by both the closures.
In this case first invocation of counter(100), would generate a closure pair ctr, intr which point to 100. Second invocation of counter(100) would generate another closure pair which point to different 100.
As you can see from output, Initially both values would be 100. and when incr() is incremented, ctr1() would be same where as ctr() would be 101. similarly when incr1() is incremented twice, ctr() would be same as 101, while ctr1() would be 102.
One obvious pitfall is creating closures within a loop. consider the following snippet.
We are creating 4 closures based on slice and returning a slice of closures. Each closure does the same - print index and the value at that index. Main function runs through all closures and calls each of them.
Lets see the output.
Surprising right!. Lets uncover this.
If we go back to closure basics, all the closures created once, have reference to same object.
In this case, all of them point to same i and same arr. When the function is actually called in
the value of i is 3, and hence all of them point to same
arr which is
4. Hence the produced output.
Hopefully this clears some air on closures and helps in understanding reading of code using closures.