Skip to content

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) = items

Struct Destructuring

let Point { x, y } = point
let Point { x: px, y: py } = point  // rename fields

For 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)  // 7

The 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.

DOCS / FEEDBACK

Did this page leave a question?

Tell us where the explanation lost you. Documentation is part of the language experience.

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

enespt-br