Guide 13 of 53
Pattern Matching and Destructuring
Exhaustive match, destructuring and guards.
On this page
Match Expression¶
match compares a value against multiple patterns:
match value {
pattern1 => expr1,
pattern2 => expr2,
_ => default_expr,
}Pattern Types¶
Literal Patterns¶
Match exact values:
match x {
0 => "zero",
1 => "one",
42 => "the answer",
_ => "other",
}Works with strings and booleans too:
match command {
"quit" => exit(),
"help" => show_help(),
_ => print("unknown command"),
}Variable Binding¶
Bind the matched value to a name:
match x {
n => print("got {n}"),
}Wildcard _¶
Matches anything without binding:
match result {
Result::Ok(value) => use(value),
_ => {}, // ignore errors
}Tuple Patterns¶
Destructure tuples:
let point = (3, 4)
match point {
(0, 0) => "origin",
(x, 0) => "on x-axis at {x}",
(0, y) => "on y-axis at {y}",
(x, y) => "({x}, {y})",
}Struct Patterns¶
Destructure structs:
struct Point { x: f64, y: f64 }
match p {
Point { x: 0.0, y: 0.0 } => "origin",
Point { x, y } => "({x}, {y})",
}Enum Patterns¶
Destructure enum variants:
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle { base: f64, height: f64 },
}
match shape {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => base * height / 2.0,
}Or Patterns |¶
Match multiple patterns:
match key {
'w' | 'W' | 'ArrowUp' => move_up(),
's' | 'S' | 'ArrowDown' => move_down(),
_ => {},
}Guard Clauses if¶
Add conditions to patterns:
match value {
n if n < 0 => "negative",
0 => "zero",
n if n > 100 => "large",
n => "normal: {n}",
}Nested Patterns¶
Patterns can be nested arbitrarily:
match event {
Event::Click { pos: Point { x, y }, button } if button == "left" => {
handle_click(x, y)
},
Event::Key { code, modifiers: Mods { ctrl: true, .. } } => {
handle_ctrl_key(code)
},
_ => {},
}Destructuring in let¶
Tuple Destructuring¶
let (x, y) = get_position()
let (first, ...rest) = itemsStruct Destructuring¶
let Point { x, y } = point
let Point { x: px, y: py } = point // rename fieldsFor Loop Destructuring¶
for (index, value) in list.enumerate() {
print("{index}: {value}")
}
for (key, val) in map.entries() {
print("{key} = {val}")
}If Let¶
Combine pattern matching with conditionals:
if let Some(value) = maybe_value {
print("Got: {value}")
} else {
print("Nothing")
}With enums:
if let Shape::Circle(radius) = shape {
print("Circle with radius {radius}")
}Chained with else-if:
if let Result::Ok(data) = fetch_data() {
process(data)
} else if let Result::Ok(cached) = read_cache() {
process(cached)
} else {
print("No data available")
}While Let¶
Loop while a pattern matches:
while let Some(item) = iterator.next() {
process(item)
}This is equivalent to:
loop {
match iterator.next() {
Some(item) => process(item),
_ => break,
}
}String patterns with typed captures¶
pat"order/{id: int}/{status: str}" matches a complete string and makes id
and status available after the entire pattern succeeds:
let description = match input {
pat"order/{id: int}/{status: str}" if id > 0 => "{id}: {status}",
_ => "invalid order",
}String captures take their text unchanged. Integer captures accept an optional
sign followed by decimal digits or 0x/0X hexadecimal digits, with exact
range checks. Whitespace, numeric separators, binary/octal prefixes and integer
exponents do not match. Boolean captures accept exactly true or false.
Floating captures use finite, checked numeric parsing. Decimal captures use the
real decimal parser on backends with decimal support. Aliases preserve the
declared type; validated newtypes run their actual checked constructor, and a
failed predicate makes the pattern fail.
Literal segments anchor the start and end. A separating literal selects its
first occurrence; matching does not backtrack to try a later delimiter. A final
capture consumes the remainder. For example, pat"{left: str}/{right: str}"
matches a/b/c as left = "a" and right = "b/c". Adjacent captures require
a literal separator, and binding names must be unique within the pattern.
Use {{ and }} for literal braces; ordinary cooked escapes retain their
authored source positions. These braces describe captures, not interpolation
expressions. Existing ordinary string patterns keep equality semantics; use an
ordinary raw string such as r"{id}" to match those literal braces exactly.
The scrutinee runs once. All conversions finish before any capture is published,
and failed matches leave no partial bindings. Capture patterns remain refutable;
keep a fallback arm. They also participate in existing successful condition,
function-clause and machine-transition pattern scopes. The attached pat"..."
literal is a pattern form; ordinary variables and functions may still be named
pat.
Stored capture pattern values¶
A capture pattern can also be stored and passed as a value:
let route = pat"users/{id: int}"
let args = route.parse("users/7").unwrap()
print(args.id) // 7The stored value has a parse function. Its success value is the exact typed
record { id: int }; the names inside the literal are result properties and
do not introduce variables in the surrounding scope. You can extract
route.parse and call that function later.
parse returns Result. A mismatch or failed conversion returns an error
record with kind: "mismatch_or_conversion", input, and pattern. Use
ordinary match, recover, or ? to handle it. All captures must succeed
before the success record is returned. An empty pattern accepts only an empty
input and returns an empty record.
Constructing a pattern does not parse input or run a validation predicate. It snapshots the actual checked constructors; parsing uses the same scalar conversions, anchoring, delimiters, and validation rules as matching above. Duplicate or invalid slots retain TE166; unsupported conversion contracts and unproved parse inputs receive TE193.
See the stored-pattern example. JavaScript emission currently reports stored capture values as unsupported.
See TE166 for invalid pattern contracts and the typed-capture example.
Hydrated structural views inside an island do not yet carry the checked client selector catalog for capture patterns. They report TE719 instead of keeping only the server-rendered branch. Use a separately compiled supported JavaScript callable or perform that matching on the server.
Integer and floating captures accept at most 4096 bytes of numeric text; string captures have no corresponding limit. Captured validated-newtype constructors require a proven closed, empty capability row. Decimal capture uses the real VM/native/LLVM parser; wasm and comptime report unsupported representation. JavaScript rejects checked nominal/decimal conversion and reports unsupported integer values outside its safe Number representation. Wasm floating conversion reuses the existing decimal accumulator, whose rounding can differ from the Rust parser for demanding inputs. This feature does not establish universal floating conversion parity for that backend.
Related examples and reference¶
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.