Optionals
Represent a value that might be missing with an optional, and unwrap it safely with if let, guard let, ??, and optional chaining.
- CS61A
- Containers (Swift-specific)
- Swift book
- The Basics · Optional Chaining
Some questions have no answer. The first element of an empty array. The
number written in the string "four". The value for a key that is not in a
dictionary. Python answers these with None, and a program that forgets to
check crashes much later with a confusing error.
Swift makes “might be missing” part of the type. That is what the ?
and ! in the last two lessons were about.
Optional types
Int? is an optional Int: either an Int, or nil, meaning no
value. Int("42") returns an Int? because the conversion can fail:
let parsed = Int("42") // Int?, holding 42
let failed = Int("four") // Int?, holding nil
print(parsed as Any, failed as Any) // Optional(42) nil
An Int? is not an Int, and Swift will not let you use it as one:
What would Swift print?
let n: Int? = 5
print(n + 1)Show answer and explanation
AnswerError
n might be nil, and nil + 1 has no meaning, so Swift refuses to
compile it: “value of optional type ‘Int?’ must be unwrapped”. Before using
the number, the program has to say what happens when there isn’t one.
That compile-time error is the whole point. Every place a value might be missing is visible in the code, and the compiler makes you handle it.
Unwrapping
if let
if let runs a block only when the optional holds a value, binding that
value to a name of the non-optional type:
let input = "42"
if let number = Int(input) {
print(number + 1) // number is an Int here
} else {
print("not a number")
}
When the new name is the same as the optional’s, if let number is enough.
Several optionals can be unwrapped at once, with extra conditions:
let first = Int("3"), second = Int("x")
if let first, let second {
print(first + second)
} else {
print("missing") // second is nil
}
guard let
guard let unwraps for the rest of the function, and requires the
else branch to leave. It keeps the normal path unindented:
func greet(_ name: String?) -> String {
guard let name else {
return "Hello, stranger"
}
return "Hello, \(name)"
}
print(greet("Ada"), "/", greet(nil))
?? and !
?? supplies a default when the optional is nil:
What would Swift print?
let a = Int("3")
let b = Int("x")
print(b ?? a ?? 0, (b ?? 1) + 1)Show answer and explanation
Answer3 2
b is nil, so b ?? a ?? 0 falls through to a, which is 3. In the
second expression, b ?? 1 is 1, and adding 1 gives 2.
! force-unwraps: it takes the value out, and stops the program if there
isn’t one. It is a promise to the compiler that you know better.
What would Swift print?
let empty: [Int] = []
print(empty.first!)Show answer and explanation
AnswerError
empty.first is nil, and force-unwrapping nil is a runtime error:
“Unexpectedly found nil while unwrapping an Optional value”. Use ! only
when nil is truly impossible, and prefer if let or ?? otherwise.
Optional chaining
?. calls a method or reads a property only if the value is there. If
anything along the chain is nil, the whole chain is nil:
let words = ["swift", "optional"]
print(words.first?.uppercased() ?? "none") // SWIFT
let nothing: [String] = []
print(nothing.first?.uppercased() ?? "none") // none
map does the same for a single transformation, and compactMap applies a
function that returns optionals to a whole sequence, keeping only the values:
print(Int("7").map { $0 * 2 } ?? 0) // 14
print(["1", "two", "3"].compactMap { Int($0) }) // [1, 3]
What would Swift print?
let scores = ["Ada": [90, 85]]
print(scores["Ada"]?.first ?? 0, scores["Alan"]?.first ?? 0)Show answer and explanation
Answer90 0
scores["Ada"] is an optional array; ?.first reads its first element, 90.
scores["Alan"] is nil, so the whole chain is nil and ?? gives 0.
Put the lines in order
Put the lines in order. The function returns nil when dividing by zero; the program prints 3 and then -1.
}func safeDivide(_ a: Int, _ b: Int) -> Int? {print(safeDivide(1, 0) ?? -1)print(safeDivide(7, 2) ?? -1)return a / bguard b != 0 else {}return nil
Show the correct program
func safeDivide(_ a: Int, _ b: Int) -> Int? {
guard b != 0 else {
return nil
}
return a / b
}
print(safeDivide(7, 2) ?? -1)
print(safeDivide(1, 0) ?? -1)Lab 10: Optionals
Q1: Safe division
Write safeDivide(_:_:), returning nil when the divisor is zero and the
integer quotient otherwise.
func safeDivide(_ a: Int, _ b: Int) -> Int? {
// your code here
}
Show solution
func safeDivide(_ a: Int, _ b: Int) -> Int? {
b == 0 ? nil : a / b
}
assert(safeDivide(7, 2) == 3)
assert(safeDivide(1, 0) == nil)
assert(safeDivide(0, 5) == 0)An Int? can be compared with == against a number or nil directly.
Q2: The first repeat
Write firstRepeated(_:), which returns the first value that appears for a
second time in the array, or nil if every value is different.
func firstRepeated(_ values: [Int]) -> Int? {
// your code here
}
Show solution
func firstRepeated(_ values: [Int]) -> Int? {
var seen: Set<Int> = []
for value in values {
if seen.contains(value) {
return value
}
seen.insert(value)
}
return nil
}
assert(firstRepeated([3, 1, 4, 1, 5, 3]) == 1) // 1 repeats before 3 does
assert(firstRepeated([1, 2, 3]) == nil)
assert(firstRepeated([]) == nil)Q3: Sum of the numbers
Write sumOfNumbers(_:), which adds up the strings in an array that are
valid integers and ignores the rest. Use no loops.
func sumOfNumbers(_ strings: [String]) -> Int {
// your code here
}
Show solution
func sumOfNumbers(_ strings: [String]) -> Int {
strings.compactMap { Int($0) }.reduce(0, +)
}
assert(sumOfNumbers(["1", "two", "3"]) == 4)
assert(sumOfNumbers(["-5", "5", "five"]) == 0)
assert(sumOfNumbers([]) == 0)compactMap does the unwrapping: failed conversions are nil and are
dropped, and only the numbers reach reduce.
What’s next
Next lesson: defining your own types with structures and enumerations.