Pattern Matching
|
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. |
A pattern is what appears on the left of a case (or after if/for/while case, or in a catch): a shape
that a value either matches or doesn’t, optionally extracting pieces of it into new constants or variables along
the way. Swift defines exactly eight pattern kinds, and the same grammar is shared by every construct that
matches something against a case.
The Eight Pattern Kinds
let point = (2, 0)
switch point {
case _: // wildcard pattern -- matches anything, binds nothing
break
}
switch point {
case let anyPoint: // identifier pattern -- matches anything, binds it to a new name
print(anyPoint)
}
switch point {
case (let x, 0): // value-binding pattern -- `let`/`var` inside a larger pattern
print("on the x-axis at \(x)")
default:
break
}
switch point {
case (0, 0): // tuple pattern -- matches each element position
print("origin")
default:
break
}
enum Direction { case north, south, east, west }
let heading = Direction.north
switch heading {
case .north: // enumeration case pattern
print("heading up")
default:
break
}
let maybeNumber: Int? = 7
switch maybeNumber {
case .some(let number): // optional pattern (`case let x?` is sugar for this)
print(number)
case .none:
print("nothing")
}
let value: Any = 42
switch value {
case is String: // type-casting pattern (`is`)
print("a string")
case let intValue as Int: // type-casting pattern (`as`), also binds
print("an Int: \(intValue)")
default:
break
}
switch 5 {
case 0..<10: // expression pattern -- matches via the `~=` operator (here, Range's)
print("single digit")
default:
break
}
| Pattern kind | Where it typically appears |
|---|---|
Wildcard ( |
anywhere a value is matched but not needed |
Identifier |
anywhere — always matches, just binds a name |
Value-binding ( |
inside a tuple, enum-case, or optional pattern |
Tuple |
|
Enumeration case ( |
|
Optional ( |
|
Type-casting ( |
|
Expression (via |
|
Where Each Pattern May Appear
for case let .some(value) in [1, nil, 3, nil, 5] { // for case: skips elements that don't match
print(value) // 1 3 5 -- nil elements are silently skipped
}
var stack = [1, 2, 0, 4]
while case let top? = stack.popLast(), top != 0 { // while case: loops only while the pattern matches
print(top)
}
do {
throw CustomError.timeout(seconds: 30)
} catch CustomError.timeout(let seconds) { // catch matches a pattern too, like switch's cases
print("timed out after \(seconds)s")
} catch {
print("some other error")
}
enum CustomError: Error { case timeout(seconds: Int) }
switch, if case, for case, while case and catch all match a value against one or more patterns using
the identical grammar above; only switch and catch require the full set of cases to be exhaustive.
Nested Destructuring
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
let shapes: [(String, Shape)] = [("a", .circle(radius: 2)), ("b", .rectangle(width: 3, height: 4))]
for (label, shape) in shapes {
switch (label, shape) { // patterns nest inside patterns freely
case let (name, .circle(radius)) where radius > 1:
print("\(name): a circle bigger than radius 1 (r = \(radius))")
case let (name, .rectangle(width, height)):
print("\(name): a \(width)x\(height) rectangle")
default:
print("\(label): something else")
}
}
let points = [(0, 0), (1, 1), (2, 4)]
for case (let x, let y) in points where y == x * x { // tuple pattern + where, nested in for case
print("(\(x), \(y)) lies on y = x^2")
}
Any pattern kind can nest inside any other — a tuple pattern’s elements can themselves be enum-case patterns,
which can themselves bind values, all matched and destructured in one case.
Custom ~= Overloads
struct Interval {
let lowerBound: Int
let upperBound: Int
}
func ~= (interval: Interval, value: Int) -> Bool { // teaches switch how to match `value`
interval.lowerBound...interval.upperBound ~= value
}
let score = 82
switch score {
case Interval(lowerBound: 90, upperBound: 100): print("A")
case Interval(lowerBound: 80, upperBound: 89): print("B") // matches via the custom ~= above
default: print("C or below")
}
An expression pattern matches by calling ~=(pattern, value) and checking whether it returns true — the
standard library already overloads it for Range/ClosedRange (matching membership) and Equatable (matching
==), and defining your own overload, as above, lets switch accept a custom type as a case pattern.
Matching Against Ranges and Regular Expressions
let bmi = 24.5
switch bmi {
case ..<18.5: print("underweight")
case 18.5..<25: print("healthy weight") // range patterns via the built-in `~=`
case 25..<30: print("overweight")
default: print("obese")
}
let line = "error: 42 issues found"
if case let match = try! Regex(#"\d+"#).firstMatch(in: line), let match {
print("first number: \(line[match.range])") // "42"
}
switch "user@example.com" {
case /\w+@\w+\.\w+/: // a regex literal used directly as a pattern
print("looks like an email address")
default:
print("not an email address")
}
A range on the left of case matches through the same ~= mechanism as any expression pattern; a Regex value
or regex literal on the left of case matches the same way, via the standard library’s ~=(Regex<Output>,
String) overload — see Regular Expressions for
Regex itself, its literal syntax, and capture groups.
How switch Evaluates Its Cases
Cases are tried strictly top to bottom; the first one whose pattern matches — and whose where clause, if any,
evaluates true — runs, and (without an explicit fallthrough) the switch exits immediately afterward,
never falling through to a later case the way C’s switch does by default.
See Also
-
Control Flow —
switch/if case/guard casein their full statement/expression context. -
Optionals — the optional pattern (
x?) in depth, andmap/flatMapas its non-pattern-matching alternative. -
Enumerations — enumeration-case patterns and associated values, the type these patterns exist to destructure.
-
Regular Expressions —
Regex, regex literals, and capture groups used above.