Why does an optional in fast enumeration cause an infinite loop?
This is an unexpected result, but it is happening because of the way Swift for in
loops work under the hood.
for in
takes a variable and a Sequence
. Swift calls makeIterator()
on the Sequence
to get an IteratorProtocol
which returns successive items when next()
is called on the iterator. next()
returns an Optional so that it can return nil
when all of the items have been exhausted.
In a normal case, you receive the non-optional values and Swift continues giving them out until nil
is received in which case the loop ends.
This is the equivalent of your code when you don't use an optional:
let array = ["what"]
var iter = array.makeIterator()
while let text = iter.next() {
print("Hello World")
}
The optional binding (while let
) fails when iter.next()
returns nil
and the loop ends.
In your case, you have said that you will explicitly receive nil
values (by declaring your loop variable as an optional), so when next()
is called on the iterator and it is out of values, it hands you a nil
which you graciously receive and the loop continues. The iterator continues to hand out nil
and you continue to take them, and you have an infinite loop.
This is the equivalent of your code when you use an optional:
let array = ["what"]
var iter = array.makeIterator()
while let text: String? = iter.next() {
print("Hello World")
}
In this case, the optional binding always works because text
can receive the nil
.
This blog gives a nice detailed explanation of what is happening under the hood with Swift for in
loops.
I say this is a bug within the Swift compiler. The array is inferred as [String]
, so a for-in loop should not allow iterating over String?
.
If you explicitly declare the array as [String?]
, then everything works as expected:
let array: [String?] = ["what"]
for text: String? in array {
print("Hello World")
}