Let's say we have:
let a:Int? = nil
// block not executed - unwapping done, type is inferred
if let unwrapped_a = a {
println(unwrapped_a)
}
// block not executed - unwrapping done, type is specified
if let unwrapped_a:Int = a {
println(unwrapped_a)
}
// block gets executed - unwrapping not done, new local constant + assignment is done instead?
if let not_unwrapped_a:Int? = a {
println(not_unwrapped_a)
}
So should I assume that Swift does an unwrapping in the first case, but an assignment in the second case?
Isn't this syntax a bit too close to create confusion? I mean, yes, the compiler warns you that you're using an optional type when working with not_unwrapped_a
, but still.
Update:
So after Airspeed Velocity's answer I found another (but actually the same) weird case:
if let not_unwrapped_a:Int???? = a {
println(not_unwrapped_a)
}
a
will be silently wrapped in an Int????
. So it will be a type of Int?????
(five) - because a
was already an optional. And then it will get unwrapped once.
Specify a Variable Type There may be times when you want to specify a type on to a variable. This can be done with casting. Python is an object-orientated language, and as such it uses classes to define data types, including its primitive types.
❌ Don't use any as a type unless you are in the process of migrating a JavaScript project to TypeScript. The compiler effectively treats any as “please turn off type checking for this thing”.
Python is a strongly-typed dynamic language in which we don't have to specify the data type of the function return value and function argument. It relates type with values instead of names. The only way to specify data of specific types is by providing explicit datatypes while calling the functions.
A type predicate is a specially-defined function that returns a boolean when a specified argument returns true. For our pets example, it would look like this: function isCat(pet: Pet): pet is Cat { return "microchip" in pet. }
Case 1 and case 2 are identical – they are both assignments of the contents of a
to a new variable. The only difference is you are leaving Swift to infer the type of unwrapped_a
in option 1, whereas you’re manually giving the type in option 2. The main reason you’d ever need to do option 2 is if the source value were ambiguous – for example if it were an overloaded function that could return multiple types.
Case 3 is quite interesting.
Whenever you have a value, Swift will always be willing to silently upgrade it to an optional wrapping the value, if it helps make the types match and the code compile. Swift auto-upgrading of types is fairly rare (it won’t implicitly upgrade an Int16
to an Int32
for example) but values to optionals is an exception.
This means you can pass values wherever an optional is needed without having to bother to wrap it:
func f(maybe: Int?) { ... }
let i = 1
// you can just pass a straight value:
f(i)
// Swift will turn this into this:
f(Optional(i))
So in your final example, you’ve told Swift you want not_unwrapped_a
to be an Int?
. But it’s part of a let
that requires a
to be unwrapped before it’s assigned to it.
Presented with this, the only way Swift can make it work is to implicitly wrap a
in another optional, so that’s what it does. Now it is an optional containing an optional containing nil. That is not a nil-valued optional - that is an optional containing a value (of an optional containing nil). Unwrapping that gives you an optional containing nil. It seems like nothing happened. But it did - it got wrapped a second time, then unwrapped once.
You can see this in action if you compile your sample code with swiftc -dump-ast source.swift
. You’ll see the phrase inject_into_optional implicit type='Int??’
. The Int??
is an optional containing an optional.
Optionals containing optionals aren’t obscure edge cases - they can happen easily. For example if you ever for…in over an array containing optionals, or used a subscript to get a value out of a dictionary that contained optionals, then optionals of optionals have been involved in that process.
Another way of thinking about this is if you think of if let x = y { }
as kind of* like a function, if_let
, defined as follows:
func if_let<T>(optVal: T?, block: T->()) {
if optVal != nil {
block(optVal!)
}
}
Now imagine if you supplied a block
that took an Int?
– that is, T
would be an Int?
. So T?
would be a Int??
. When you passed a regular Int?
into if_let
along with that block, Swift would then implicitly upgrade it to an Int??
to make it compile. That’s essentially what’s happening with the if let not_unwrapped_a:Int?
.
I agree, the implicit optional upgrade can sometimes be surprising (even more surprising is that Swift will upgrade functions that return optionals, i.e. if a function takes an (Int)->Int?
, it will upgrade an (Int)->Int
to return an optional instead). But presumably the feeling is the potential confusion is worth it for the convenience in this case.
* only kind of
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With