I'm studying Swift's closures but I have some questions about closure capture lists. I know that closures are a reference type. I know the meaning of reference type and value type. But in a closure, when they capture the values (local variables of closures), how can it be that local variables are reference type?
For example:
var i: Int = 0
var closureArray: [() -> ()] = []
for _ in 1...5 {
closureArray.append { print(i) }
i += 1
}
I think this is the most famous example of capture list in closure. Then I thought i
is the integer type and it is also value type but how can i
be the reference type in closure blocks?
Is this the same concept with scope chain in JavaScript? Can anyone explain it clearly?
While a closure itself is a reference type, the local variables it captures are not necessarily references. If you think of a closure conceptually as an executable instance of a
class
that has the captured variables as properties, you're surprising close to the way they are implemented (unless they are marked@convention(c)
in which case they are just function pointers, but in that case, they can't capture anything). All of this is subject to optimization, such as inlining.Of course, if the local variable is itself a reference type, for example, an instance of a
class
, it is captured by reference.Immutable local value type variables are captured by value though. For example, if your code were:
You'd get
Mutable local value type variables are captured by reference, which works in a way very similar to
inout
parameters to functions. Under the hood, what is actually captured is a pointer to the mutable value.Originally, I said that such closures cannot be
@escaping
, and I'm pretty sure that was true at one point, but your question prompted me to do an experiment that shows otherwise:The closures escape the function scope via the returned
Array
, and yet capture the local variable,i
, by reference. Despite the closures being run afterfoo
returns, the output is:I even tried running a series of deeply nested functions between getting to thoroughly smash the stack between obtaining the closure array from
foo
and calling the closures, and it had no effect on the output.It seems that it's boxing
i
rather than actually putting it on the stack. This implies that Swift boxes the local variable as well (ie. when used infoo
outside of the closure). That makes sense, because it allows the closure to safely be executed, though it does introduce some potential performance degradation since the box has to be dynamically allocated. I put the code into Compiler Explorer. This is the Intel Assembly for the linevar i: Int = 0
:Note the call to
swift_allocObject@PLT
. That's dynamically allocating the box fori
. So the thing thatfoo
and the closure both use when they accessi
is a pointer to that allocated memory.As a comparison, if I remove the closures altogether:
Now the Assembly line corresponding to
var i: Int = 0
is just one instruction:So capturing a mutable variable not only affects how the variable is accessed in the closure, but also in the scope in which that variable was declared.
I don't program in JavaScript, so I'm not qualified to compare captured variables in Swift with any feature of JavaScript.