Optionals
An array finds an item by position. A dictionary finds a value by key.
[String: Int] pairs text keys with whole-number values; its literal uses
["tea": 2]. Ask for a key it does not have, and there is no Int to hand
back:
let stock: [String: Int] = ["tea": 2]
let tea: Int? = stock["tea"]
let coffee: Int? = stock["coffee"]
if let amount = tea {
print("Tea: \(amount)")
}
if let amount = coffee {
print("Coffee: \(amount)")
} else {
print("No coffee")
}
Tea: 2
No coffee
Int? means an Int or nil, the absence of a value. Ordinary lookup
returns that optional type. stock["tea"] contains 2; stock["coffee"]
is nil. Neither is a plain Int until we handle the possibility that the
key is missing. A missing value is not the same thing as the integer zero.
if let amount = tea opens the optional only when it holds a value. Inside
that branch, amount is an immutable Int, so interpolation can use it
without further checks. The binding lives in the branch, not below it.
coffee is missing, so its else branch prints instead.
When missing means zero
Sometimes a count of zero is the answer you want even if the key is absent:
let stock: [String: Int] = ["tea": 2, "milk": 1]
let tea = stock["tea"] ?? 0
let bread = stock["bread"] ?? 0
print("Tea: \(tea)")
print("Bread: \(bread)")
Tea: 2
Bread: 0
?? uses the value on its left when present and the fallback on its right
when nil. Tea has 2; bread is missing, so this lookup prints 0 without
adding bread to stock. The fallback answers the question; it does not
restock the shelf. Unlike if let, this expression gives back an Int in
both cases. The fallback expression runs only for a missing value.
Keep the useful path outside the braces
In a function, guard let lets us deal with the missing key first:
func servings(for item: String, from stock: [String: Int]) -> Int {
guard let amount = stock[item] else {
return 0
}
return amount * 2
}
let stock: [String: Int] = ["tea": 2]
print("Tea servings: \(servings(for: "tea", from: stock))")
print("Coffee servings: \(servings(for: "coffee", from: stock))")
Tea servings: 4
Coffee servings: 0
If the lookup is nil, the else returns 0 from servings. When the
lookup succeeds, amount is an immutable Int for the rest of the
function, including return amount * 2. Chapter 5’s Bool guard had the
same rule for its else: it must leave the surrounding function or closure.
Here the condition opens an optional instead of testing a Bool.
Supply a fallback, or ask only if present
Suppose a receipt might not have been made yet. We can ask for its label without pretending the receipt exists:
struct Receipt {
let total: Int
func label() -> String {
return "Total: \(total)"
}
}
func fallback() -> String {
print("Looking for fallback")
return "No receipt"
}
let present: Receipt? = Receipt(total: 4)
let missing: Receipt? = nil
print(present?.label() ?? fallback())
print(missing?.label() ?? fallback())
print("Forced total: \(present!.total)")
Total: 4
Looking for fallback
No receipt
Forced total: 4
Receipt? may contain a receipt or nil. The nil on the missing line
needs a type context; the annotation supplies it here.
present?.label() calls label() only when there is a receipt, returning
String?. On nil, it skips the call and returns nil. ?? chooses a plain
String from either that result or fallback(). The fallback call runs only
when needed: that is why Looking for fallback appears once, on the second
print.
The last print uses present! to insist on a receipt. It works because
present holds one. If we wrote missing!, the program would fail at
runtime. Use a binding or a fallback for ordinary absence; ! is an
assertion that the value is there.