问题
Why is
if let y: Int? = nil { ... }
the same as
if let y: Int? = nil as Int?? { ... }
(and thus an invalid assignment) especially when, on its own
let y: Int? = nil
is not the same as
let y: Int? = nil as Int??
(since let y: Int? = nil
is a valid assignment)?
回答1:
OK, I will answer, with my poor English skills ;-)
Let's start with this:
if let lvalue:T = rvalue { ... }
At first the compiler tries to convert rvalue
to T?
by wrapping with Optional
. For example:
typealias T = Int
let rvalue:Int? = 1
if let lvalue:T = rvalue { ... } // do nothing because `rvalue` is already `T?`
//---
typealias T = Int??
let rvalue:Int = 1
if let lvalue:T = rvalue { ... } // rvalue will be converted to `T?`, that is `Int???`
//---
typealias T = Int
let rvalue:Int?? = 1
if let lvalue:T = rvalue { ... } // error because `rvalue` could not be converted by wrapping with Optional
Then the runtime look into converted rvalue
by unwrapping once whether that value is nil
or not. If not nil
then assign and success.
This is the rule for if let lvalue:T = rvalue { ... }
On the other hand,
let lvalue:T = rvalue
It's similar but not the same. The compiler tries to convert rvalue
to T
, not T?
.
typealias T = Int??
let rvalue:Int?? = 1
let lvalue:T = rvalue // Do nothing because `rvalue` is `T`
//---
typealias T = Int??
let rvalue:Int = 1
let lvalue:T = rvalue // rvalue will be converted to `T`, that is `Int??`
Then the runtime can unconditionally assign rvalue
to lvalue
.
I think this is the difference.
You you want to observe these compiler works, you can use swiftc -dump-ast
command.
$ cat test.swift
let i:Int? = 1
if let y:Int? = i { }
$ xcrun swiftc -dump-ast test.swift
(source_file
(top_level_code_decl
(brace_stmt
(pattern_binding_decl
(pattern_typed type='Int?'
(pattern_named type='Int?' 'i')
)
(inject_into_optional implicit type='Int?' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14]
(call_expr implicit type='Int' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14]
(constructor_ref_call_expr implicit type='(_builtinIntegerLiteral: Int2048) -> Int' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14]
(declref_expr implicit type='Int.Type -> (_builtinIntegerLiteral: Int2048) -> Int' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) specialized=no)
(type_expr implicit type='Int.Type' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14] typerepr='<<IMPLICIT>>'))
(tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14] names=_builtinIntegerLiteral
(integer_literal_expr type='Int2048' location=test.swift:1:14 range=[test.swift:1:14 - line:1:14] value=1)))))
)
(var_decl "i" type='Int?' access=internal let storage_kind='stored')
(top_level_code_decl
(brace_stmt
(if_stmt
(pattern_binding_decl
(pattern_typed type='Int?'
(pattern_named type='Int?' 'y')
)
(inject_into_optional implicit type='Int??' location=test.swift:2:17 range=[test.swift:2:17 - line:2:17]
(declref_expr type='Int?' location=test.swift:2:17 range=[test.swift:2:17 - line:2:17] decl=test.(file).i@test.swift:1:5 specialized=no)))
(brace_stmt))))
回答2:
Think about what optional binding is for. It allows you to take an optional value, and condition on whether it is nil
or not, and if it is not nil
, to unwrap the contained value and bind it to a variable. So it is like this:
if let non_optional_var = optional_expr {
...
} else {
...
}
Thus if optional_expr
has type T?
, then non_optional_var
has type T
. (When I wrote "non_optional_var" I didn't mean literally that it can't be optional, but to express that it has one less level of optional-ness than the "optional_expr". Thus, if non_optional_var
is type Int?
, then optional_expr
has type Int??
.
By the way, the optional binding syntax is syntactic sugar for switching on the Optional
enum:
switch optional_expr {
case .Some(let non_optional_var):
...
case .None:
...
}
来源:https://stackoverflow.com/questions/26615711/what-does-swifts-optional-binding-do-to-the-type-its-arguments