The Nil-Coalescing Operator
Sprout's habit card shows the reminder time, or "No reminder" when there is none. With if let and else that takes five lines. The nil-coalescing operator does it in one, and it is one of the most used operators in real Swift.
The rule
a ?? b: useaif it is there, otherwiseb.
a is an optional. If it holds a value, the result is that value, unwrapped. If it is nil, the result is b, the default.
let reminder: String? = nil
let shown = reminder ?? "No reminder" // "No reminder"
let typed = "15"
let minutes = Int(typed) ?? 0 // 15
let bad = Int("fifteen") ?? 0 // 0Three facts make it predictable:
- The result is not optional.
shownis a plainString, ready to use. - The default must match the wrapped type. With a
String?on the left, the default must be aString.reminder ?? 0does not compile. - The default is only worked out when needed. If
ahas a value,bis never evaluated, so an expensive default costs nothing most of the time.
You can chain defaults. Swift tries each one left to right and takes the first that is not nil:
let title = customName ?? suggestedName ?? "New habit"It is a short form of this:
let shown: String
if let reminder { shown = reminder } else { shown = "No reminder" }Use ?? when there is a sensible default. When nil needs different behaviour, not just a different value, if let or guard let is clearer.
The shortcut
Read ?? as "or else". Look at the left side: value present? Take it. nil? Move one step right and ask again. The last item in a chain should be non-optional, so the answer is always a plain value.
The line
let title = customName ?? suggestedName ?? "New habit"withcustomNamenil andsuggestedName"Drink water".
Drills
Answer fast — trust the rule.
With let note: String? = "Stretch first", what is note ?? "None"?
What is Int("abc") ?? -1?
With let r: String? = nil, why does r ?? 0 fail to compile?
With a and b both String? values set to nil, what is a ?? b ?? "c"?
Remember
a ?? b: useaif it is there, otherwiseb.- Read
??as "or else"; the result is never optional when the last item is not. - The default must match the wrapped type, and is only evaluated when needed.
