Optional binding of nil literal vs. variable that is nil in Swift

In Swift, why does

var x: Int? = nil
if let y: Int? = x { ... }

behave differently from

  • Cannot invoke “+=” with an argument list of type (Int, @value Int)
  • Multiple variable assignment in Swift
  • myView.frame.origin.x = value; does not work - But why?
  • Declared but unset variable evaluates as true?
  • “Expression is not assignable” — Problem assigning float as sum of two other floats in xCode?
  • What does assigning a literal string to an NSString with “=” actually do?
  • if let y: Int? = nil { ... }
    

    My understanding of why the first case succeeds suggests that the second should as well, so I must not really be understanding.

    The latter is not failing because of an invalid assignment, nor because of optional chaining; and otherwise it seems the same as the former. Why does the latter fail, and how is it different from the former. Exactly at what point, and for what reason, is the second optional binding abandoned?

    3 Solutions Collect From Internet About “Optional binding of nil literal vs. variable that is nil in Swift”

    if let y: Int? = nil { ... }
    

    is equivalent to

    if let y: Int? = nil as Int?? { ... }
    

    is equivalent to

    if let y: Optional<Int> = Optional<Optional<Int>>() { ... }
    

    This tries to cast Optional<Optional<Int>> to Optional<Int>, and it fails because Optional<Optional<Int>>() does not contains Optional<Int> value.


    Oneliner equivalent to

    var x: Int? = nil
    if let y: Int? = x { ... } 
    

    is

    if let y: Int? = nil as Int? { ... } 
    

    ADDED:

    As I answered on What does Swift's optional binding do to the type it's arguments?.

    if let y: Int? = nil as Int? { ... }
    

    is compiled as:

    if let y:Int? = nil as Int? as Int?? { ... }
    

    Here, we should mind that nil as Int? as Int?? is not equivalent to nil as Int??.
    The former is Optional(Optional<Int>()), but the latter is Optional<Optional<Int>>().

    With this in mind, I think,

    So the optional binding in my first example (does indeed) “check inside” and finds nil, which is perfectly valid to assign to Int?; but my second checks and finds Optional(nil), which can’t be assigned to Int?

    The first example “check inside” of Int?? and just finds Optional<Int>() value that is Int? type which can be assigned to Int?; But the second one finds nothing to be assigned and fails.

    let declares a constant that is set at initialization time. Using the let in an if statement with an Optional<T>, then it binds the initialized constant the the result of a failable initializer, not literally to nil or another literal.

    The compiler allowed you to use nil directly, instead of ‘4’ for e.g., since nil has use/context outside of Optional; however, this literal use of nil bypasses the initializer, so there is no result to bind to. In the cases of 4 or Int(4), the compiler knows something is awry and won’t compile.

    Review the following example:

    var xnil: Int? = nil
    var x4: Int? = Int?(4)
    var xnone: Int? = Int?()
    var xdefault: Int? = Int()
    
    if let znil: Int? = nil {
        println("znil:\(znil)")
    } else {
        println("znil: did not bind")
    }
    
    if let zn0: Int? = Int?(nilLiteral: ()) {
        println("zn0:\(zn0)")
    }
    
    if let zn1: Int? = Int?(()) {
        println("zn1:\(zn1)")
    }
    
    if let zn2: Int? = Int?() {
        println("zn1:\(zn2)")
    }
    
    if let zn3: Int? = Int?.None {
        println("zn3:\(zn3)")
    }
    
    if Int?.None == nil {
        println(".None == nil")
    }
    
    if let zo0: Int? = Int?(4) {
        println("zo0:\(zo0)")
    }
    
    //nil-test.swift:36:20: error: bound value in a conditional binding must be of Optional type
    //if let zo1: Int? = 4 {
    //                   ^
    /*
    if let zo1: Int? = 4 {
        println("zo1:\(zo1)")
    }
    */
    
    //nil-test.swift:51:20: error: bound value in a conditional binding must be of Optional type
    //if let zo2: Int? = Int(4) {
    //                   ^
    /*
    if let zo2: Int? = Int(4) {
        println("zo2:\(zo2)")
    }
    */
    
    
    if let zxn0: Int? = xnil {
        println("zxn0:\(zxn0)")
    }
    
    if let zxn1: Int? = x4 {
        println("zxn1:\(zxn1)")
    }
    
    if let zxn2: Int? = xnone {
        println("zxn2:\(zxn2)")
    }
    
    if let zxn3: Int? = xdefault {
        println("zxn3:\(zxn3)")
    }
    

    … it outputs:

    znil: did not bind
    zn0:nil
    zn1:nil
    zn1:nil
    zn3:nil
    .None == nil
    zo0:Optional(4)
    zxn0:nil
    zxn1:Optional(4)
    zxn2:nil
    zxn3:Optional(0)
    

    UPDATE: Explaining the difference between Type and Type?.

    See this example:

    if let a: Int? = nil {
        println("a: \(a)")
    } else { 
        println("a: let fail")
    }
    
    if let b1: Int? = Int?(nilLiteral: ()) { // same as Int?() -- added nilLiteral to be explicit here
        println("b1: \(b1)")
    }
    
    if let b2: Int? = Int?(44) {
        println("b2: \(b2); b2!: \(b2!)")
    }
    
    if let c1: Int = Int?(44) {
        println("c1: \(c1)")
    }
    
    if let c2: Int = Int?(nilLiteral: ()) { // Again, Int?() would suffice
        println("c2: \(c2)")
    } else {
        println("c2: let fail")
    }
    
    /// NOTE: these 'd' examples represents a more idiomatic form
    var m: Int? = Int?()
    if let d1 = m {
        println("d1: \(d1)")
    } else {
        println("d1: let fail")
    }
    
    m = Int?(444)
    if let d2 = m {
        println("d2: \(d2)")
    } else {
        println("d2: let fail")
    }
    
    m = Int?()
    println("m?: \(m?)")
    if let whyDoThis: Int? = m {
        println("whyDoThis?: \(whyDoThis?) -- the `let` is telling you nothing about 'm!'")
    }
    

    … it outputs:

    a: let fail
    b1: nil
    b2: Optional(44); b2!: 44
    c1: 44
    c2: let fail
    d1: let fail
    d2: 444
    m?: nil
    whyDoThis?: nil -- the `let` is telling you nothing about 'm!'!
    

    … so, ask yourself:

    • Why did a fail, and b1 bind successfully, even though it contains a nil value?
    • Why does if let whyDoThis ... succeed when m clearly contains a nil value at that point?
    • And what does the value of whyDoThis? tell you about m!?

    In the end, idiomatic Swift pseudocode, for this case, should look like the following:

    var maybeT: MyType?
    // ... maybe set maybeT to a MyType, maybe not
    if let reallyHaveT = maybeT {
      // reallyHaveT has a bound value of type MyType
    }
    

    UPDATE 2: Okay, let’s look at types …

    See the following example:

    var none: Int? = Int?()
    var one: Int? = Int?(1)
    
    println("Int()   type: \(_stdlib_getTypeName(Int())), val: \(Int())")
    println("Int(1)  type: \(_stdlib_getTypeName(Int(1))), val: \(Int(1))")
    println("Int?()  type: \(_stdlib_getTypeName(Int?())), val: \(Int?())")
    println("Int?(1) type: \(_stdlib_getTypeName(Int?(1))), val: \(Int?(1))")
    println("none    type: \(_stdlib_getTypeName(none)), val: \(none)")
    println("one     type: \(_stdlib_getTypeName(one)), val: \(one)")
    
    if let x = none {
        println("`let x = none`       x type: \(_stdlib_getTypeName(x))")
    } else {
        println("`let x = none`       FAIL")
    }
    if let x: Int = none {
        println("`let x: Int = none`  x type: \(_stdlib_getTypeName(x))")
    } else {
        println("`let x: Int = none`  FAIL")
    }
    if let x: Int? = none {
        println("`let x: Int? = none` x type: \(_stdlib_getTypeName(x))")
    }
    
    if let y = one {
        println("`let y = one`        y type: \(_stdlib_getTypeName(y))")
    }
    if let y: Int = one {
        println("`let y: Int = one`   y type: \(_stdlib_getTypeName(y))")
    }
    if let y: Int? = one {
        println("`let y: Int? = one`  y type: \(_stdlib_getTypeName(y))")
    }
    
    if let z: Int? = nil {
        println("`let z: Int? = nil`  z type: \(_stdlib_getTypeName(z))")
    } else {
        println("`let z: Int? = nil`  FAIL")
    }
    if let z = Int?() {
        println("`let z = Int?()`     z type: \(_stdlib_getTypeName(z))")
    } else {
        println("`let z = Int?()`     FAIL")
    }
    

    … it outputs:

    Int()   type: _TtSi, val: 0
    Int(1)  type: _TtSi, val: 1
    Int?()  type: _TtSq, val: nil
    Int?(1) type: _TtSq, val: Optional(1)
    none    type: _TtSq, val: nil
    one     type: _TtSq, val: Optional(1)
    `let x = none`       FAIL
    `let x: Int = none`  FAIL
    `let x: Int? = none` x type: _TtSq
    `let y = one`        y type: _TtSi
    `let y: Int = one`   y type: _TtSi
    `let y: Int? = one`  y type: _TtSq
    `let z: Int? = nil`  FAIL
    `let z = Int?()`     FAIL
    

    The point I want to stress is that you are effectively invoking let z = Int?() when you write let z: Int? = nil. This will fail to bind when used in an if statement. Always.

    if let is used to get rid of the optional. If you want to see when the variable is nil use

    if let y: Int = x {
    // This occurs if you are able to give a non-nil value to the variable
    } else {
    // This occurs if the optional x is nil
    }
    

    as far as I know you shouldn’t try declare the type of a constant in the if let statement to be optional because that kind of defeats the purpose of the if let statement.