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")
}

Tags:

Swift