What an optional is
In many languages, any variable can secretly hold a null value, and the program fails when you use it. Swift makes absence part of the type. A plain String always holds a string. When a value might be missing, the type says so with a question mark: String?. This is an optional. It holds either a wrapped String or nil, and the compiler won't let you use it as a plain String until you have dealt with both cases.
Dictionaries are where you meet optionals first. A lookup can miss, so the subscript returns the value type wrapped in an optional:
let capitals = ["France": "Paris", "Japan": "Tokyo"]
print(capitals["Japan"] as Any) // Optional("Tokyo")
print(capitals["Brazil"] as Any) // nil
A missing key is not an error. It produces nil. The compiler stops you from forgetting that possibility: let s: String = capitals["France"] is rejected with an error saying the value of optional type String? must be unwrapped, and calling capitals["France"].count is rejected for the same reason. Getting the value out is called unwrapping.
Four ways to unwrap
1. if let
if let runs a block only when a value is present, and binds it to a new constant that exists only inside the block:
if let city = capitals["Japan"] {
print("got \(city)") // city is a plain String here
}
If the lookup gives nil, the block is skipped and nothing crashes. You can attach an else block for the missing case.
2. guard let
guard let works the other way around. It states what must be true to continue, and handles the failure first. The unwrapped constant stays available after the guard, for the rest of the enclosing scope. The else block must leave that scope, for example with return, otherwise the compiler rejects the code:
func describe(_ country: String) {
let capitals = ["France": "Paris", "Japan": "Tokyo"]
guard let city = capitals[country] else {
print("unknown country")
return
}
print(city) // a plain String from here on
}
describe("France") // Paris
describe("Peru") // unknown country
3. The nil-coalescing operator ??
a ?? b gives the value inside a when there is one, and b otherwise. It turns an optional into a plain value in one expression:
let city = capitals["Brazil"] ?? "unknown" // "unknown"
4. Force unwrapping with !
A ! after an optional takes the value out without any check:
let city = capitals["Japan"]! // "Tokyo"
This is safe only when the value must exist. If it is nil, the program crashes at run time. Code such as capitals["Brazil"]! compiles without complaint and then stops the program with a fatal error when it runs. Use if let, guard let or ?? by default, and keep ! for values you can prove are there.
The exercise, step by step
- Create the dictionary.
capitalsmaps"France"to"Paris","Japan"to"Tokyo"and"Italy"to"Rome". Swift infers the type[String: String]from the literal, so no annotation is needed. - Store a lookup.
let lookup = capitals["Japan"]doesn't hold aString. Its type isString?, a wrapped value ornil. - Unwrap with if let.
if let city = lookup { ... }bindscityinside the braces. Inside,print("Japan -> \(city)")uses the plain string. - Unwrap with guard let.
guard let france = capitals["France"] else { return }exits if the key is missing. Because of that,franceis a plainStringon the lines after it. - Use ?? inside the interpolation. The key
"Brazil"isn't present, socapitals["Brazil"] ?? "unknown"supplies the fallback. - Force-unwrap Italy. The key exists, so
capitals["Italy"]!is safe here.
let capitals = ["France": "Paris", "Japan": "Tokyo", "Italy": "Rome"]
let lookup = capitals["Japan"]
if let city = lookup {
print("Japan -> \(city)")
}
guard let france = capitals["France"] else {
return
}
print("France -> \(france)")
print("Brazil -> \(capitals["Brazil"] ?? "unknown")")
print("Italy -> \(capitals["Italy"]!)")
The expected output is:
Japan -> Tokyo
France -> Paris
Brazil -> unknown
Italy -> Rome
Inside string interpolation, the quotes of the dictionary keys and of the fallback are fine, because everything between \( and ) is ordinary Swift code.
Common mistakes
- Printing an optional directly. Interpolating or printing a
String?showsOptional("Tokyo")rather thanTokyo. Unwrap first. - Forcing a value that may be missing. A
!on a missing key crashes the program. A typo in a key is enough. - Using the constant outside if let. The constant bound by
if letexists only inside its braces. Aguard letconstant, in contrast, lives on after the statement. - A guard else that falls through. The block must end with
returnor another exit such asbreakorcontinuein a loop. Otherwise the code doesn't compile. - Using
??with the wrong type. The fallback must match the wrapped type:capitals["Brazil"] ?? 0is an error for a dictionary of strings.
Key takeaways
T?means a value of typeTornil, and the compiler enforces the check.- A dictionary subscript always returns an optional, because the key may be missing.
if letunwraps for one block,guard letunwraps for the rest of the scope,??supplies a default, and!crashes onnil.
