Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the nil coalescing operator ?? returning nil?

Tags:

swift

optional

Consider the following example involving the nil coalescing operator ??:

let mysteryInc = ["Fred": "Jones", "Scooby-Doo": nil]

let lastname = mysteryInc["Scooby-Doo"] ?? "no last name"

print(lastname == nil)  // true

As shown by the last print statement, the result of the nil coalescing operator is nil.

If the nil coalescing operator is supposed to unwrap an Optional, why is it returning nil?

like image 328
vacawama Avatar asked Jun 02 '17 13:06

vacawama


1 Answers

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").

like image 74
vacawama Avatar answered Oct 29 '22 17:10

vacawama