Ruta graveolens  ·  notes from a language experiment  ·  cultivated since 2025

Match Expressions

A match expression provides multi-way branching based on pattern matching.

match_expr = "match" expression "{" { match_arm "," } [ match_arm ] "}" ;
match_arm = pattern "=>" expression ;
pattern = "_" | [ "-" ] INTEGER | BOOL | enum_variant_pattern ;
enum_variant_pattern = IDENT "::" IDENT ;

Patterns

A pattern is irrefutable if it matches any value of its type. A pattern is refutable if there exist values of its type that it does not match.

The wildcard pattern _ is irrefutable. It matches any value.

An integer literal pattern is refutable. It matches only the specific integer value it denotes.

A boolean literal pattern (true or false) is refutable. It matches only the specific boolean value it denotes.

An enum variant pattern is refutable. It matches only values of that specific variant.

Exhaustiveness

A set of patterns is exhaustive for a type if every possible value of that type is matched by at least one pattern in the set.

A match expression MUST have an exhaustive set of patterns for its scrutinee type. A match expression with a non-exhaustive pattern set is rejected with a compile-time error.

The following rules determine whether a pattern set is exhaustive:

  1. Any pattern set containing an irrefutable pattern is exhaustive.
  2. For type bool: a pattern set containing both true and false is exhaustive.
  3. For an enum type: a pattern set containing a pattern for every variant of that enum is exhaustive.
  4. For integer types: only rule (1) applies; explicit enumeration of integer values is not sufficient to establish exhaustiveness.
fn main() -> i32 {
    match 2 {
        1 => 10,
        2 => 20,
        _ => 0,  // wildcard required for integer scrutinees
    }
}

Type Checking

All match arms MUST have the same type. The type of the match expression is the common type of its arms.

The type of each pattern MUST be compatible with the type of the scrutinee. A pattern with an incompatible type is rejected with a compile-time error.

Arm Bodies

Match arm bodies MAY be simple expressions or block expressions.

fn main() -> i32 {
    match 2 {
        1 => 10,
        2 => {
            let x = 20;
            x + 5
        },
        _ => 0,
    }
}

Execution

Arms are evaluated in order. The first arm whose pattern matches the scrutinee value is selected, and its body expression is evaluated. The result of that evaluation becomes the value of the match expression.

Unreachable Patterns

A pattern is unreachable if all values it could match are already matched by a preceding pattern in the same match expression.

A pattern following an irrefutable pattern (such as _) is always unreachable, since the irrefutable pattern matches all possible values.

A pattern that is identical to a preceding pattern in the same match expression is unreachable, since the earlier pattern will match first.

An unreachable pattern produces a compile-time warning. The program remains well-formed and the unreachable arm is not executed at runtime.

fn main() -> i32 {
    match 5 {
        _ => 10,
        1 => 20,  // warning: unreachable pattern '1'
    }
}
fn main() -> i32 {
    match 1 {
        1 => 10,
        1 => 20,  // warning: unreachable pattern '1'
        _ => 0,
    }
}

Pattern Range Requirements

An integer literal pattern MUST denote a value representable in the scrutinee's type. A pattern whose value is out of range for the scrutinee type is rejected with a compile-time error, exactly as an out-of-range integer literal in any other position (3.1:17). A negated literal that denotes the minimum value of a signed scrutinee type remains valid (3.1:18).

A negative integer literal pattern MUST NOT be used with a scrutinee of unsigned type. Such a pattern is rejected with a compile-time error; unsigned values are never negative, so the arm could never match.

fn main() -> i32 {
    let x: u32 = 0;
    match x {
        4294967296 => 1,  // error: out of range for u32
        -1 => 2,          // error: negative pattern on unsigned scrutinee
        _ => 0,
    }
}

Empty Match Expressions

A match expression with zero arms is legal if and only if the scrutinee's type is an enum with zero variants. Such a type has no values, so the empty pattern set vacuously satisfies exhaustiveness (4.7:8). A match expression with zero arms on any other type is rejected with a compile-time error.

The type of a match expression with zero arms is ! (the never type): the expression can never be reached with a scrutinee value, so it never produces a value.

A struct literal MUST NOT appear as the outermost expression of a match scrutinee; a program that requires one parenthesizes the scrutinee. Consequently, in match v {} the braces denote the match expression's empty arm list, not a struct literal v {}.

enum Never {}

fn absurd(n: Never) -> i32 {
    match n {}  // legal: zero arms cover the zero values of `Never`
}

Patterns with Payload Bindings

A tuple-variant pattern binds the variant's payload into fresh names: EnumName::Variant(a, b) matches a value of that variant and binds a, b to its payload fields in order. The number of bindings MUST equal the variant's payload arity. Payload bindings require the enum_payloads preview feature (see spec 6.3).

Payload bindings inherit the scrutinee's access mode (ADR-0037/ADR-0038). A bare match e uses the scrutinee in value context: the matched arm's bindings move the payload out of the enum (or copy it, if the payload type is Copy). Each binding is in scope for its arm's body and shadows any outer binding of the same name.

enum Shape { Circle(i32), Rect(i32, i32), Empty }

fn main() -> i32 {
    match Shape::Rect(3, 4) {
        Shape::Circle(r) => r,
        Shape::Rect(w, h) => w + h,
        Shape::Empty => 0,
    }
}