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.