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

2

-2

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)?

Nympha answered 28/10, 2014 at 18:23 Comment(9)
I think you're starting to veer off into territory that would be handled much better on Apple's Swift dev forum. The actual Swift team answers questions there, so you'll be able to get more definitive answers and have a back and forth to get the details of this.Sturgill
@NateCook: Not sure where why. Can you explain? This seems no different from any other question here: I need to know what's going on so I can avoid coding errors.Nympha
I'm not saying you shouldn't ask here, just that you'll probably have better luck in the dev forums (or on #swiftlang, honestly). It seems more like you're exploring the fringes of how optionals and optional binding works than actually dealing with writing code; if let y: Int? = nil { } completely misses the point of optional binding, so I don't see how this is a problem you're actually running into.Sturgill
@NateCook: I thought would reveal something about how optional binding was working. Since it seems central to understanding why var x: Int? = nil; if let y: Int? = x { ... } works. It's the key that unlocks both those other questions. Otherwise none of it make much sense (for me anyway). I also thought it would be the right place for @rintaro to explain where his answer came from.Nympha
possible duplicate of Why does Swift's optional binding succeed with 'nil' in certain cases?Honeycutt
@SteveRosenberg: Actually it's crucial to figuring out what's going in in that question. The answer there assumes this, but this ins't explained there.Nympha
Okay, didn't realize.Honeycutt
Have you posted this over at the Apple Dev forums? Likely you are deep into the inner workings of Swift here and need the Apple team to respond definitively.Honeycutt
@SteveRosenberg: Good idea. I'm curious about that too. Mostly here the question is what the I see coding (since it's not what I expect and seemed arbitrary). I think rintaro's answer has cleared it up though.Nympha
P
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)[email protected]:1:5 specialized=no)))

        (brace_stmt))))
Pannier answered 29/10, 2014 at 6:55 Comment(6)
Thanks! So in conditional binding, rvalue is first and always wrapped in Optional; and only then unwrapped once to see if assignment works? And in (regular) assignment, it is there is no wrapping (of course) and rvalue is just checked to see if it can be converted to the type of lvalue?Nympha
Yes I think so. IMO, you should check that out with swiftc -dump-ast. You can see when and how the compiler inserts inject_into_optionals.Pannier
Hmmm. So if the steps really are 1: Optional binding, if any (stop on nil otherwise evaluate and wrap in Optional); 2: Wrap (again, always?); 3: Find what's "inside" (==unwrap?); 4: Attempt assignment of result, then I understand the above, but I don't understand what's happening with if let y3: Int = x1? {...} or if let y4: Int = x1 { ... }, if we've done x1 = 42. These seem to end up at step 4 with Optional(42) which can't be assigned? Do I not have the steps right? Are they not all always performed?Nympha
Maybe step 2 (the wrapping you explain) only happens if rvalue is not and optional binding expression (but still happens even when it is already of optional type)?Nympha
Sorry, but I'm not English native. It's very hard to discuss in English for me.Pannier
That's OK: anything helps! The question seems to boil down to: When is wrapping added to rvalue, and at what point?Nympha
M
1

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:
    ...
}
Maura answered 31/10, 2014 at 1:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.