Closures in Go¶
A Simple Closure¶
Informally, a closure is an object that refers to both a function and an environment of variables that the function can access.
For example, consider makeAdder1
:
func makeAdder1(n int) func(int) int {
f := func(a int) int {
return n + a // n refers to the n passed-in to makeAdder1
}
return f // n escapes from makeAdder1
}
func test1() {
add1 := makeAdder1(1)
fmt.Println(add1(add1(3)))
}
It takes an int
n
as input and returns a new function f
with
signature func(int) int
, such that f(a)
returns n + a
. The
function f
is a closure because f
refers to a variable (namely n
)
that was not defined inside f
.
A Closure for Generating Fibonacci Numbers¶
The Fibonacci numbers are the sequence 1, 1, 2, 3, 5, 8, 13, …. The first two numbers are 1 and 1, and then every number after those equals the sum of the two numbers before, e.g. 13 = 5 + 8.
Here’s a nice function that returns a closure that, when called, returns the next Fibonacci number:
// genFib() returns a function that, every time it is called,
// returns the next Fibonacci number
func genFib() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func test() {
nextFib := genFib()
//
// prints the first 10 Fibonacci numbers
//
for i := 0; i < 10; i++ {
fmt.Printf("nextFib() = %v\n", nextFib())
}
}
A More Sophisticated Closure¶
In this next example, the function makeAdder2
returns two closures that
share the same environment:
func makeAdder2() (func() int, func() int) {
i := 0
add := func() int {
i++ // i is the i local to makeAdder2 defined above
return i
}
get := func() int {
return i // i is not local to get
}
return add, get // i escapes from makeAdder2
}
func test2() {
incr, get := makeAdder2()
fmt.Println(get())
incr()
incr()
fmt.Println(get())
}
makeAdder2
returns two closures: the first adds 1 to the closed-over
variable i
, and the second returns the current value of i
.
The tricky part here is that the two returned closures refer to the variable
i
that is local in makeAdder2
. If i
were stored on the call stack
(like regular local variables), then when makeAdder2
ends i
would no
longer exist — causing add/get to not work!
So for these two closures to work correctly, i
needs to be stored in a
location that is still accessible after makeAdder2
is done. Go
automatically determines when a variable like i
“escapes” from a function,
and can thus ensure that i
is stored in an accessible location.
Notice that the add
and get
closures share the same i
variable.
They thus operate on the same underlying variable, which makes them similar to
methods in object-oriented programming.
Questions¶
In your own words, describe what a closure is. Try to be as clear and precise as possible.
What does it mean when we say that a variable escapes from a function?
How does Go handle closures that refer to variables that have been defined outside of the closure?
Make a function called
genMult(n int)
that returns a closuref()
such that every timef()
is called it returnsn
, and after that ever time it’s called it returns a value that isn
more than the previous value.For example:
f := genMult(5) fmt.Println(f()) // returns 5 fmt.Println(f()) // returns 10 fmt.Println(f()) // returns 15
Modify
makeAdder2
to return one more closure calledreset()
that, when called, sets the value of the closed-over variablei
to 0. Test it to make sure it works correctly.