Home > Back-end >  Swift confused about capturing values in swift and locality of variables
Swift confused about capturing values in swift and locality of variables

Time:07-05

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

So I'm reading swift documentation and they define some function here with a closure inside it that basically uses the variables that are defined locally in makeIncrementer now this makes sense to me but then this is said:

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.

Now to me this doesn't make sense? Capturing by reference ensures that runningTotal and amount don't disappear when the call to makeIncrementer ends? When that call ends don't those variables just get freed and the memory for them is basically just gone? They don't exist anymore? Until I call that function again in which the memory is allocated for them again but they are new variables am I wrong here?

And why would runningTotal not be available the next time the incrementer function is called? It's always created and initalized in makeIncrementer how would there ever be a case that incrementer cannot access runningTotal?

CodePudding user response:

Capturing by reference ensures that runningTotal and amount don't disappear when the call to makeIncrementer ends? When that call ends don't those variables just get freed and the memory for them is basically just gone?

The key thing to notice here is that makeIncrementer returns a new function object, which is what has captured those variables. Normally, you would be correct: runningTotal is a local variable, so at the end of makeIncrementer, it would normally "go away".

Except, the incrementer function still needs to access it; the compiler notices this, and effectively extends the lifetime of the variable outside of the scope of makeIncrementer. Its lifetime is tied to the lifetime of the function object you return, and when that object goes away, so does the runningTotal variable. If you store the result of calling makeIncrementer in a property, for example, the runningTotal that was created inside of makeIncrementer would stick around until you assigned something else to that property; if you call makeIncrementer and don't do anything with the result, though, then it would go away immediately, as it's not needed anymore.

Until I call that function again in which the memory is allocated for them again but they are new variables am I wrong here?

Every time you call makeIncrementer, you create a new local runningTotal variable that is distinct and separate from every other runningTotal variable created by other calls to makeIncrementer. Each of those variables is separately held on to by different functions created by every single call to makeIncrementer totally separately.

So:

  1. Yes, every time you call makeIncrementer, memory is used for a new runningTotal variable, as it's a new variable, and
  2. Because each new call creates a new variable which is captured by a newly-created function object, each individual incrementer will always have access to the runningTotal it captures — the point of the capture is to ensure that the variable will be accesible so long as the function object could be called

And why would runningTotal not be available the next time the incrementer function is called? It's always created and initalized in makeIncrementer how would there ever be a case that incrementer cannot access runningTotal?

If the compiler didn't take care to extend the lifetime of the runningTotal variable, then when makeIncrementer returned an incrementer function object, the runningTotal memory would "go away" and potentially get reused for something else entirely (because the variable is local). If incrementer would try to use that runningTotal reference it thinks it has, it would be manipulating random memory, which would cause some serious problems.

Instead, Swift makes sure that this is safe, and that it can't go wrong.

  • Related