Jamie Cockburn
Jamie Cockburn

Reputation: 7555

Weird optional type behaviour in swift forEach

This code works fine. It iterates my array of one Int! and prints its magnitude.

import Foundation

let x : Int! = 1

[x].forEach {i in
    print(i.magnitude)
}

Output:

1

Presumably, i in the loop body is an Int or an Int!, and indeed if I ask Xcode for "quick help" on forEach it reports:

func forEach(_ body: (Int) throws -> Void) rethrows

However, if perform two statements in my forEach body, instead it fails to compile, complaining that I need to unwrap i which now has the optional type Int?.

import Foundation

let x : Int! = 1

[x].forEach {i in
    print(i.magnitude)
    print(i.magnitude)
}

Compile error:

Value of optional type 'Int?' must be unwrapped to refer to member 'magnitude' of wrapped base type 'Int'

And if I ask for "quick help" now I get:

func forEach(_ body: (Int?) throws -> Void) rethrows

How on earth does the number of statements I place in my loop body manage to affect the type of the loop variable?

Upvotes: 4

Views: 217

Answers (1)

matt
matt

Reputation: 535547

Basically, you've elicited an edge case of an edge case. You've combined two things that are the work of the devil:

  • Implicitly unwrapped Optionals
  • Implicit type inference of closures, along with the fact that
    • Implicit type inference of closures works differently when the closure consists of one line (this is where the "How on earth does the number of statements" comes in)

You should try to avoid both of those; your code will be cleaner and will compile a lot faster. Indeed, implicit type inference of anything other than a single literal, like a string, Int, or Double, is a huge drag on compilation times.

I won't pretend to imitate the compiler's reasoning; I'll just show you an actual solution (other than just not using an IUO in the first place):

    [x].forEach {(i:Int) in
        print(i.magnitude)
        print(i.magnitude)
    }

Our Int type is legal because we take advantage of the single "get out of jail free" card saying that an implicitly unwrapped Optional can be used directly where the unwrapped type itself is expected. And by explicitly stating the type, we clear up the compiler's doubts.

(I say "directly" because implicit unwrappedness of an Optional is not propagated thru passing and assignment. That is why in your second example you discovered Int?, not Int, being passed into the closure.)

Upvotes: 3

Related Questions