The problem with a plain lookup
A missing key often means "none yet". A shop with no plums has 0 plums. But a plain lookup on a missing key does not give 0. It gives nil, and the type is Int?, a number that might be absent. Forcing it open with ! crashes the program when the key is missing.
let stock: [String: Int] = ["apples": 5]
print(type(of: stock["plums"]))
Optional<Int>
The default: subscript
A dictionary has a second subscript that takes a fallback: stock["plums", default: 0]. It gives the key's value when the key exists, and the default when it doesn't. Either way, the result is a plain Int, not an optional.
print(stock["apples", default: 0])
print(stock["plums", default: 0])
5
0
Reading this way does not add the key. After the lines above, stock.count is still 1.
The ?? alternative
The nil-coalescing operator gives the same answer for a read. stock["plums"] ?? 0 means "the value, or 0 if there is none". Both forms are fine for reading. The default: form has an extra use for writing, as in counts[word, default: 0] += 1, which counts words in one line. That is for a later lesson, but it explains why the form is worth knowing.
Choosing between default: and ??
For reading, pick whichever reads better in your line. stock["plums", default: 0] says the fallback belongs to the lookup. stock["plums"] ?? 0 says "if there is no value, use 0", and it also works on any other optional, not just dictionary lookups. If you need to know whether the key was there at all, use neither: compare the plain lookup with nil instead.
Why the default matters for writing
The same subscript also works on the left of an assignment. Counting how often each word appears is the classic case:
var counts: [String: Int] = [:]
for word in ["red", "blue", "red"] {
counts[word, default: 0] += 1
}
print(counts["red", default: 0], counts["blue", default: 0], counts["green", default: 0])
2 1 0
The first time a word shows up, the default 0 is used, and 1 is stored. Without the default, a missing key would give nil and the addition would not compile. This is the same idea as the lesson's exercise, in the other direction.
Traps
- Choosing a sensible default. It must make sense for the question. 0 fits a count. For a name,
""may fit better. - A read does not store the default. After
stock["plums", default: 0], the count is still 1, andstock["plums"] == nilis still true. - Keys must match exactly, letter case included.
stock["Plums", default: 0]looks for a different key.
The exercise: counting fruit
stock counts the fruit in a shop. The starter code reads and prints the pears. The pears exist, so the plain lookup with ! is safe there:
let stock: [String: Int] = ["apples": 5, "pears": 2]
let pears = stock["pears"]!
print("We have " + String(pears) + " pears")
There are no plums. Read "plums" with default: 0 and store it in plums. Then print the plums line the same way as the pears line, with String(plums) turning the number into text:
let plums = stock["plums", default: 0]
print("We have " + String(plums) + " plums")
The whole program prints:
We have 2 pears
We have 0 plums
If you had used stock["plums"]! here, the program would have crashed on the missing key before it printed the line.
Key takeaways
dict[key]is optional.dict[key, default: d]is a plain value.- The default is returned for a missing key and is not stored.
dict[key] ?? dis another way to read with a fallback.
