Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Surprise results iterating over an array with an optional in Swift

Tags:

swift

swift4

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?

like image 371
HalR Avatar asked Nov 02 '18 19:11

HalR


1 Answers

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)
}
like image 92
Martin R Avatar answered Nov 15 '22 07:11

Martin R