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

  1. In your own words, describe what a closure is. Try to be as clear and precise as possible.

  2. What does it mean when we say that a variable escapes from a function?

  3. How does Go handle closures that refer to variables that have been defined outside of the closure?

  4. Make a function called genMult(n int) that returns a closure f() such that every time f() is called it returns n, and after that ever time it’s called it returns a value that is n 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
    
  5. Modify makeAdder2 to return one more closure called reset() that, when called, sets the value of the closed-over variable i to 0. Test it to make sure it works correctly.