Optionals
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
An optional says, in the type itself, whether a value is present. Int? is not "an Int that might secretly
be null" the way a reference type is in most other languages — it is a distinct type the compiler forces you
to unwrap before using the value inside.
nil and the Optional Enum
Optional<Wrapped> is a genuine two-case enum in the standard library, case none and case some(Wrapped);
T? and nil are syntactic sugar over it:
var serverResponseCode: Int? = 404
serverResponseCode = nil // now holds "no value" -- legal only because it's an Optional
var surveyAnswer: String? // no initial value -- automatically nil
print(surveyAnswer == nil) // true
// What `Int?` desugars to:
let sameThing: Optional<Int> = .some(404)
switch sameThing {
case .none: print("no value")
case .some(let value): print("value is \(value)")
}
Only a type declared with ? (or explicitly Optional<T>) can hold nil — a plain Int or String can
never be nil, in contrast to reference types in languages without null safety.
Optional Binding
Optional binding — if let/guard let/while let — tests whether an optional holds a value and, if so,
makes that value available as a non-optional constant (or variable) for a scope:
let possibleNumber = "123"
if let actualNumber = Int(possibleNumber) {
print("\"\(possibleNumber)\" has an integer value of \(actualNumber)")
} else {
print("\"\(possibleNumber)\" could not be converted to an integer")
}
// Swift 5.7's shorthand: bind to a new constant of the SAME NAME as the optional.
if let actualNumber {
print(actualNumber)
}
// Multiple bindings, and a `where` clause, in one `if let`; evaluated left to right,
// each earlier binding available to later clauses -- and short-circuits on the first failure.
func nextSquare(after n: Int?, limit: Int?) -> Int? {
if let n, let limit, n < limit {
return n * n
}
return nil
}
guard let binds the same way but requires the else branch to exit the current scope (return, throw,
break, continue, or fatalError) — the bound value then stays in scope for the rest of the enclosing
function, rather than only inside an `if’s braces:
func greet(_ person: [String: String]) -> String {
guard let name = person["name"] else {
return "Hi there, stranger!"
}
// `name` is available, unwrapped, for the rest of this function -- not just one branch.
guard let location = person["location"] else {
return "Hi \(name)!"
}
return "Hi \(name)! Glad you could visit from \(location)."
}
while let repeats a loop body for as long as a binding keeps succeeding, stopping the first time it yields
nil:
var stack = [1, 2, 3, 4, 5]
while let top = stack.popLast() {
print(top) // 5 4 3 2 1
}
The Nil-Coalescing Operator
a ?? b unwraps a if it is non-nil, or evaluates and returns b otherwise; b must have the same type as
a’s wrapped value (or be itself optional), and it is evaluated lazily, only when `a is nil:
let defaultColorName = "red"
var userDefinedColorName: String?
var colorNameToUse = userDefinedColorName ?? defaultColorName // "red"
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName // "green"
a ?? b is shorthand for a != nil ? a! : b, but without the force-unwrap risk — see
Operators for where it sits in the precedence table.
Forced Unwrapping and Implicitly Unwrapped Optionals
The forced-unwrap operator ! accesses an optional’s value directly, trapping (crashing) if it is nil:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
if convertedNumber != nil {
print("has an integer value of \(convertedNumber!)") // safe -- checked just above
}
// let crash = Int("not a number")! // fatal error: unexpectedly found nil while unwrapping
Reach for ! only where you have already proven the value is non-nil in a way the compiler cannot see
itself; optional binding is almost always the better default.
An implicitly unwrapped optional, written T!, is declared optional (and can still hold nil) but is
automatically force-unwrapped every time it is used as a non-optional T, without needing ! at each call
site:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // needs the `!`
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no `!` needed -- unwrapped automatically
if assumedString != nil { // still usable as a normal optional
print(assumedString!)
}
if let definiteString = assumedString { // and still bindable
print(definiteString)
}
T! exists almost entirely for values that are set once, immediately after initialization, and never nil
again in practice — a @IBOutlet, or a two-phase-initialized property — where writing T? everywhere would
force unwraps at every use site for a value that is, in practice, always present. Prefer a genuine T? when
there is any real chance the value is absent.
Optional Chaining
Optional chaining — ?./?[…] after an optional — calls a property, method, or subscript only if the
optional is non-nil, and the whole expression evaluates to nil immediately if any link in the chain is nil,
without crashing:
class Residence {
var numberOfRooms = 1
var address: Address?
}
class Person {
var residence: Residence?
}
class Address {
var buildingName: String?
func printAddress() { print("some address") }
}
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.") // printed -- john.residence is nil
}
john.residence?.address?.printAddress() // no-op, no crash: the whole chain short-circuits
let count = john.residence?.address?.buildingName?.count // Int?, chained through three optionals
Every step in a chain, and the whole expression’s result, is optional — even a property that is itself
Int becomes Int? once accessed through ?., because any link along the way might have been nil. Calling
a method through a chain that returns Void yields Void?, useful for testing whether the call happened at
all: if (someOptional?.doSomething()) != nil { … }.
The Optional Pattern, map/flatMap, and Comparing Optionals
The optional pattern matches only the .some case, binding its payload, and reads as the value’s type
followed by ?:
let someOptional: Int? = 42
if case let x? = someOptional { // equivalent to `if case .some(let x) = someOptional`
print(x)
}
for case let number? in [1, nil, 3, nil, 5] { // skips every nil element
print(number) // 1 3 5
}
map transforms the wrapped value in place, staying optional; flatMap does the same but flattens away a
result that would otherwise be a nested optional:
let possibleNumber: Int? = 4
let possibleSquare = possibleNumber.map { $0 * $0 } // Optional(16)
func rootIfPositive(_ n: Int) -> Int? { n >= 0 ? Int(Double(n).squareRoot()) : nil }
let nested = possibleNumber.map(rootIfPositive) // Int?? -- an optional optional
let flattened = possibleNumber.flatMap(rootIfPositive) // Int? -- flattened back down
Optionals conform to Equatable/Comparable whenever their Wrapped type does, and nil compares equal
only to nil and always sorts before every wrapped value:
print(Int?.none == Int?.none) // true
print(Optional(1) == Optional(1)) // true
print(Optional(1) == nil) // false
print([3, nil, 1, nil, 2].sorted { ($0 ?? Int.min) < ($1 ?? Int.min) }) // a common trick for sorting with nils first
See Also
-
Basics: Constants, Variables and Types —
let/varand the type system optionals build on. -
Operators — `??’s place in the precedence table.
-
Pattern Matching — the optional pattern in the context of every other pattern Swift supports.
-
Error Handling —
throws/Result, for failure that needs to carry a reason, not just absence.