What do closures print when they capture a loop variable, a shared variable, or a copy in a capture list?
Predict first
What does this print?
var fns: [() -> Int] = []
for i in 0..<3 { fns.append { i * 10 } }
print(fns.map { $0() })
var j = 0
var gs: [() -> Int] = []
while j < 3 { gs.append { j }; j += 1 }
print(gs.map { $0() })
var k = 0
var hs: [() -> Int] = []
while k < 3 { hs.append { [k] in k }; k += 1 }
print(hs.map { $0() })
var x = 1
let byRef = { x }
let byCopy = { [x] in x }
x = 5
print(byRef(), byCopy())
- [0, 10, 20] / [3, 3, 3] / [0, 1, 2] / 5 1
- [0, 10, 20] / [0, 1, 2] / [0, 1, 2] / 1 1
- [20, 20, 20] / [3, 3, 3] / [2, 2, 2] / 5 5
Mental model. A closure holding a variable is holding the whole notebook, so later edits show up. A capture list photocopies the page at that moment, so the copy stays as it was.
A closure captures a variable, not a snapshot of its value, unless you write a capture list. In for i in 0..<3 each iteration has its own fresh constant i, so the three closures capture three different variables and print 0, 10, 20. In the while loop there is one var j shared by everything; the closures all read it later, after the loop has made it 3, so they print [3, 3, 3]. The capture list [k] evaluates k at the moment the closure is created and stores that copy, so each one remembers 0, 1, 2.
The last line is the same idea in miniature: byRef reads x when it is called (5), while [x] froze 1. This is value-type capture. For a class instance, a capture list copies the reference, so both closures still see the same object’s changes (compare Q32, which does this with a struct and a stored property). The practical fix when you want the old behaviour back is a capture list, or a let copy made just before creating the closure.
Each for iteration has its own constant, a shared while variable is read at call time, and a capture list copies at creation.
Common traps
- Thinking
whileandforloops behave alike. Aforvariable is new per iteration; avardeclared outside the loop is one shared variable. - Expecting
[x]on a class to freeze its contents. It copies the reference only; changes to the object’s properties are still visible.
They might follow up with
- How does this change for an escaping closure stored for later? Nothing in the rules; escaping just means the capture outlives the call, so the shared variable is read later.
- What is a closure’s captured variable stored in? A heap box shared between the closure and the scope, which is why they stay in sync (Q36).
Related questions
- 27.1.1 In what order do initialisers and deinitialisers run for a subclass with stored properties, and what does…
- 27.1.3 A closure uses [weak self] and the owner is released while the work is running. What is the difference…
- 27.4.4 What is a reliable routine for answering “what does this print?” under interview pressure?
- 1.5.4 What does a capture list like [weak self], [unowned self], [self], or [foo] actually do?
- 1.5.5 Why does this print the new city without a capture list, and the old one with [city]?
- 1.5.3 Why are closures called reference types? What is a closure context?
← In what order do initialisers and deinitialisers run for a subclass… A closure uses [weak self] and the owner is released while the work… →