问题
In Swift, why does
var x: Int? = nil
if let y: Int? = x { ... }
behave differently from
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?
回答1:
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.
回答2:
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, andb1
bind successfully, even though it contains a nil value? - Why does
if let whyDoThis ...
succeed whenm
clearly contains a nil value at that point? - And what does the value of
whyDoThis?
tell you aboutm!
?
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.
回答3:
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.
来源:https://stackoverflow.com/questions/26613195/optional-binding-of-nil-literal-vs-variable-that-is-nil-in-swift