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

前端 未结 3 1468
醉梦人生
醉梦人生 2021-01-07 13:50

In Swift, why does

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

behave differently from

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


        
相关标签:
3条回答
  • 2021-01-07 14:26

    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.

    0 讨论(0)
  • 2021-01-07 14:35

    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.

    0 讨论(0)
  • 2021-01-07 14:41
    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.

    0 讨论(0)
提交回复
热议问题