To see what is going on here, assign the dictionary look up to a constant:
let name = mysteryInc["Scooby-Doo"]
print(type(of: name))
Output:
Optional<Optional<String>>
So, name
is a double Optional, a String??
.
When the nil coalescing operator is applied, it unwraps the outer Optional and leaves an Optional<String>
(aka String?
). In the example in the question, Swift treats the String literal "no last name"
as type String?
so that ??
can be applied.
If you examine the value of name
you will find that it is Optional(nil)
. All dictionary look ups return Optionals because the key might not exist (in which case they return nil
). In this case, the mysteryInc
dictionary is of type [String: String?]
. The value corresponding to "Scooby-Doo"
is nil
which then gets wrapped into an Optional (because of the dictionary look up) to become Optional(nil)
.
Finally, the value Optional(nil)
is unwrapped by ??
. Since Optional(nil) != nil
, Swift unwraps Optional(nil)
and returns nil
instead of returning the expected "no last name"
.
And that is how you can get nil
from the nil coalescing operator. It did unwrap the Optional; there just happened to be a nil
inside of that Optional.
As @LeoDabus noted in the comments, the correct way to deal with this situation is to conditionally cast the name to String
before applying the nil coalescing operator:
let lastname = mysteryInc["Scooby-Doo"] as? String ?? "no last name"
In reality, it is best to avoid having Optionals as values for your dictionary for the very reason that @Sulthan raises:
What does mysteryInc["Fred"] = nil
do?
Assigning nil
to a dictionary entry removes that entry from the dictionary, so mysteryInc["Fred"] = nil
doesn't replace Optional("Jones")
with nil
, it removes the "Fred"
entry altogether. To leave "Fred"
in the dictionary and make his last name nil
, you need to assign mysteryInc["Fred"] = Optional(nil)
or mysteryInc.updateValue(nil, forKey: "Fred")
.