How does a closure capture local variables by reference?

After reading this post, I have felt that the following question is not adequately exaplained in TSPL.

How does a closure capture local variables by reference?

TSPL provides an excellent example but doesn't really explain the mechanics of capturing by reference.

From TSPL:

Capturing Values

A closure can capture constants and variables from the surrounding context in which it’s defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.

In Swift, the simplest form of a closure that can capture values is a nested function, written within the body of another function. A nested function can capture any of its outer function’s arguments and can also capture any constants and variables defined within the outer function.

Here’s an example of a function called makeIncrementer, which contains a nested function called incrementer. The nested incrementer() function captures two values, runningTotaland amount, from its surrounding context. After capturing these values, incrementer is returned by makeIncrementer as a closure that increments runningTotal by amount each time it’s called.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

When considered in isolation, the nested incrementer() function might seem unusual:

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

The incrementer() function doesn’t have any parameters, and yet it refers to runningTotal and amount from within its function body. It does this by capturing a reference to runningTotal and amount from the surrounding function and using them within its own function body. Capturing by reference ensures that runningTotal and amount don’t disappear when the call to makeIncrementer ends, and also ensures that runningTotal is available the next time the incrementer function is called.

Someone, after reading the passage above, may still wonder: But, after the func makeIncrementer returns, the local variable allocated on the stack goes out of scope and is destroyed. Then, how can the closure still reference that variable?

They might ask: How does a closure capture local variables by reference? What are the mechanics of it?

The question is answered in sufficient details by this post, thanks to @Jumhyn.

I’ll note that nowhere in those first chapters of TSPL does it say local bindings are on “the stack”. It doesn’t even talk about a stack. So while it might be an interesting question for someone more familiar with programming, it doesn’t necessarily belong in the “guide” section of the book. If every local variable in Swift were a separate, reference-counted dynamic allocation, you’d have the same overall semantics. I encourage thinking of putting variables directly on the stack as an optimization, just like these days we usually think of putting variables in registers as an optimization.

6 Likes