What does Swift's optional binding do to the type it's arguments?

纵饮孤独 提交于 2019-12-06 16:27:09

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!