I was surprised that this swift code behaves nicely:
let values = ["Hello", "Test"]
var count = 0
for string: String in values {
count = count + 1
print("count is: ", count)
print(string)
}
with output of:
count is: 1
Hello
count is: 2
Test
but making the String into String? creates an infinite loop.
let values = ["Hello", "Test"]
var count = 0
for string: String? in values {
count = count + 1
print("count is: ", count)
print(string)
}
with output of:
count is: 1
Optional("Hello")
count is: 2
Optional("Test")
count is: 3
nil
count is: 4
nil
count is: 5
nil
count is: 6
nil
count is: 7
nil
count is: 8
(ad infinitum)
Swift has been so good at catching weird code problems that I was surprised I could walk into such a mess without warning or error. Is this really what one would expect from Swift 4? And if so, why?
To understand this issue it helps to recollect how for-in loops work:
for s in values {
print(s)
}
creates an iterator of the sequence, and calls the iterator's next()
method until that returns nil
:
var it = values.makeIterator()
while let s = it.next() {
print(s)
}
Your second version is equivalent to
var it = values.makeIterator()
while let s: String? = it.next() {
print(s)
}
and now the compiler warns:
warning: explicitly specified type 'String?' adds an additional level of optional to the initializer, making the optional check always succeed while let s: String? = it.next() { ^ ~~~~~~~ ~~~~~~~~~ String
So what happens here is that the String?
returned from it.next()
is wrapped into a “nested optional” .some(it.next())
of type String??
, which is then optionally bound to s: String?
.
This does always succeed, because .some(it.next())
is not String??.none
.
Therefore the loop never terminates.
One could argue that the compiler should warn as well for
for s: String? in values {
print(s)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With