5

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?

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 1
    Related: [Check if key exists in dictionary of type `[Type:Type?]`](https://stackoverflow.com/questions/29299727/check-if-key-exists-in-dictionary-of-type-typetype) – Martin R Jun 02 '17 at 13:46
  • 2
    Also, an interesting use case: `mysteryInc["Scooby-Doo"] = nil`. What will it do? – Sulthan Jun 02 '17 at 13:48
  • 2
    @Sulthan, `mysteryInc["Fred"] = nil` removes `Fred` entirely from the dictionary. `mysteryInc["Fred"] = Optional(nil)` replaces "Jones" with `nil`. – vacawama Jun 02 '17 at 14:58
  • Related [Checking for nil Value in Swift Dictionary Extension](https://stackoverflow.com/questions/36018238/checking-for-nil-value-in-swift-dictionary-extension), [How to add nil value to Swift Dictionary?](https://stackoverflow.com/questions/26544573/how-to-add-nil-value-to-swift-dictionary) – Sulthan Jun 02 '17 at 18:11

1 Answers1

10

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

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 2
    You need to first cast as string then use the nil coalescing operator `as? String ?? "no last name"` – Leo Dabus Jun 02 '17 at 13:45
  • @LeoDabus, thanks for the suggestion. Interestingly, this has only worked since Swift 3.1. In Swift 3.02 and earlier it gives: **Error: downcast from 'String??' to 'String' only unwraps optionals; did you mean to use '!!'?** – vacawama Jun 02 '17 at 14:54
  • You are welcome. regarding the prior syntax I have no Idea. I would never use an optional value type inside a Dictionary so I've never tried before. I generally use a struct. – Leo Dabus Jun 02 '17 at 14:57
  • BTW Interesting the Optional(nil) stuff – Leo Dabus Jun 02 '17 at 14:59
  • 1
    I would probably prefer using `.updateValue` method directly instead of assigning `dict[key] = Optional(nil)`. – Sulthan Jun 02 '17 at 15:01
  • 1
    Yeah. I typically avoid optional values in dictionaries as well. In this example, it actually has a use. `mysteryInc["Scooby-Doo"] == Optional(nil)` is `true` meaning `Scooby-Doo` is in `mysteryInc` but doesn't have a last name. `mysteryInc["Speed Buggy"] == nil` is true meaning `Speed Buggy` isn't in `mysteryInc`. – vacawama Jun 02 '17 at 15:02