A missing key gives nil
A dictionary lookup in Swift never fails for a key that isn't there. The subscript returns an optional: the value when the key exists, and nil when it doesn't. For a dictionary of type [String: String], a lookup has type String?.
let capitals: [String: String] = ["France": "Paris", "Japan": "Tokyo"]
print(type(of: capitals["Spain"]))
Optional<String>
That gives a simple test. capitals["Japan"] != nil is true when "Japan" is a key, and capitals["Spain"] == nil is true when "Spain" is not.
if capitals["Japan"] != nil {
print(capitals["Japan"]!)
}
if capitals["Spain"] == nil {
print("No Spain")
}
Tokyo
No Spain
Why the ! is safe in one place only
The ! after a lookup forces the optional open. On a missing key that crashes the program, because there is no value to unwrap. Inside the first if above, the key was just checked, so the ! is safe. Outside such a check, capitals["Spain"]! would stop the program.
When you want the check and the value together, if let does both:
if let city = capitals["Japan"] {
print(city)
}
Tokyo
The body runs only when the key exists, and city is already a plain String, so no ! is needed.
Which form to pick
Use != nil or == nil when you only need a yes or no, as in "is this name taken?". Use if let when you need the value as well. It looks the key up once, and it can never crash, so it is the safer habit. The lesson's exercise uses the != nil form with ! on purpose, to show why the check and the unwrap belong together.
Checking several keys
The same test works in a loop. Here each country is looked up with if let, and the else branch handles a missing key:
for country in ["France", "Spain", "japan"] {
if let city = capitals[country] {
print(country + " -> " + city)
} else {
print(country + " is not a key")
}
}
France -> Paris
Spain is not a key
japan is not a key
The third name is not found only because of its lowercase first letter. That is how strict the match is.
Traps
- A value is not a key. A lookup uses only the keys. In
capitals, the lookupcapitals["Paris"]isnil, because"Paris"is a value. - Keys must match exactly, letter case included:
capitals["japan"]isnil. - Don't treat the result as a plain value.
let x: String = capitals["Japan"]doesn't compile, because the lookup is optional.
There is also capitals.keys.contains("France"), which asks the same question about the keys explicitly. The != nil form is the one this lesson uses.
One more point: a key whose value happens to be an empty string is still a key. ["a": ""]["a"] is Optional(""), not nil, so the check != nil is true. Only a truly absent key gives nil.
The exercise: pets
In pets, each key is a pet's name and each value is the pet's kind:
let pets: [String: String] = ["Rex": "dog", "Tom": "cat", "Polly": "parrot"]
First, check that "Tom" is a key, and print Tom is a followed by his kind. The ! is safe because the if just checked. Then check that "cat" is not a key. It is in pets, but only as a value: it is Tom's kind. The lookup pets["cat"] is nil, so the second message prints.
let pets: [String: String] = ["Rex": "dog", "Tom": "cat", "Polly": "parrot"]
if pets["Tom"] != nil {
print("Tom is a " + pets["Tom"]!)
}
if pets["cat"] == nil {
print("cat is not a key")
}
Tom is a cat
cat is not a key
Key takeaways
- A lookup returns an optional: the value, or
nilfor a missing key. dict[key] != nilasks whether the key exists, and== nilasks the opposite.- Use
!only right after a check, or useif let. - Only keys are searched, never values.
