Wado

Patterns

A pattern tests a value's shape and binds names to its parts. The same pattern syntax appears wherever a value is taken apart: match arms, if let, while let, let … else, matches, let and for-of bindings, and parameters. The statements themselves are in Control Flow.

Pattern Syntax

Pattern Example Description
Wildcard _ Matches anything
Variable x Binds matched value
Mut variable mut x, Some(mut x) Binds as mutable
Literal 0, "hello", true Matches exact value
Constant MAX_LEN, i32::MAX Matches an immutable global / const by value
Range 0..<60, 'a'..='z' Matches an integer, char or float in range
Variant Some(x), None Matches variant case
Tuple [a, b, c] Destructures tuple
Nested tuple [10, Some(x)] Literal/variant sub-patterns in tuple
Struct { x, y }, Point { x, y } Destructures struct
Nested struct { x: 0, y } Literal/variant sub-patterns in struct
Or Red \| Blue Matches either pattern
Guard Some(x) && x > 0 Pattern with condition
Type input: HtmlInputElement Matches when the value is of the type

A tuple pattern names every element of the tuple, or ends in .. after at most that many. Over a tuple carrying a variadic pack, only the elements ahead of the pack have a fixed position, so the pattern names at most those and ends in ...

A string-literal pattern tests the scrutinee with == against a String, so any type implementing Eq<String> matches one: a String, a StrSlice, or a newtype over either. A type parameter bounded by AsStrSlice matches as well.

A float literal is not a pattern. A float matches a range pattern instead.

A literal or range pattern on a value whose type is a type parameter means, in each instance, what it means on that instance's type. An instance whose type the literal is not, or cannot hold its value, is an error at the pattern.

fn is_minus_one<T>(x: T) -> i32 {
    return match x {
        -1 => 1,
        _ => 0,
    };
}

test "a negative literal matches each signed width" {
    assert is_minus_one(builtin::black_box(-1_i8)) == 1;
    assert is_minus_one(builtin::black_box(-1_i64)) == 1;
    assert is_minus_one(builtin::black_box(1_i32)) == 0;
}
fn kind<S: AsStrSlice>(s: S) -> i32 {
    return match s { "cntrl" => 1, "digit" => 2, _ => 0 };
}

test {
    assert kind("digit".as_str_slice()) == 2;
}

Patterns That Cannot Fail

A let or for binding takes a pattern that matches every value of its type. A case pattern does so when no other case of the type holds a value (Exhaustiveness). A literal, a range, a constant, an or-pattern, and a narrowing type pattern never do.

variant W { A(i32) }

test {
    let w = W::A(1);
    let r: Result<i32, !> = Ok(2);
    let A(x) = w;                     // OK: W has no other case
    let Ok(v) = r;                    // OK: r has no `Err`
    assert x == 1 && v == 2;
}

A case pattern of a type with another case is an error, since that case may be the value:

let opt: Option<i32> = Option::Some(1);
let Some(y) = opt;

A bare name in such a pattern, at its root or below it, is a case pattern when it names a case of the type it matches, and a binding otherwise. It never names a global: a constant pattern can always fail, so reading one here could only be rejected. So let [None, n] = pair tests its first element, and is an error since None may not match. let limit = 1 binds even where a global limit is in scope, and shadowed_name warns. A refutable pattern reads such a name differently (Constant Patterns).

An uninitialized let x: T; declares a single name, or _.

Exhaustiveness

A match must cover every value of its scrutinee. A _ arm covers whatever the other arms leave:

let name = match color {
    Red => "red",
    Green => "green",
    _ => "other",  // Required for exhaustiveness
};
assert name == "other";

The guardless arms must cover every value together. A guarded arm covers nothing, and a case is covered only as far as its payload patterns reach. This match misses Some(-2147483648..=0), among others:

let s = match opt {
    Some(1) => "one",
    None => "none",
};

An arm no value can reach is an error too: the guardless arms before it already take every value it matches.

let n = match opt {
    _ => 0,
    Some(x) => x,
};

A case whose payload type has no value needs no arm, since nothing constructs it. An arm naming one is unreachable, and is the same error. A type has no value when it is !, when a tuple element or struct field of it has none, or when every case of a variant has a payload with none:

let r: Result<i32, !> = Ok(1);
let n = match r {
    Ok(v) => v,     // exhaustive: no `Result<i32, !>` is an `Err`
};
assert n == 1;

Guard Expressions

A guard follows the pattern after &&, since it runs after the pattern matches, and only then:

let discount = match customer {
    Premium(years) && years > 5 => 0.3,
    Premium(_) => 0.2,
    _ => 0.1,
};
assert discount == 0.3;

Qualified Patterns

A case may be written under the type that declares it: Color::Green. Two kinds of qualifier reach the same cases.

The first is a name that resolves to the scrutinee's type. An import alias (M::Nothing under use { Maybe as M }), Self inside an impl, and a namespace-qualified type (dep::Maybe::Nothing) all qualify. So does any name on the scrutinee's newtype chain, a newtype's cases being its base's: with type C = Color, both C::Green and Color::Green qualify. A second newtype over the same base does not, being a distinct type.

The second is a namespace prefix the scrutinee's type is reachable through, such as h::Green under use h from "./hue.wado". That prefix names a module rather than a type.

Either way the name must be one the file can see. Only the prelude is in scope without a use, so a qualifier naming an unimported type is an error even where the bare case would match.

A qualifier may restate the scrutinee's type arguments. It must then write as many as the scrutinee carries, so Maybe<i32>::Just qualifies a Maybe<i32> while Color<i32>::Red is an error, because Color declares no type parameters.

Only a bare identifier can bind. A qualified path names a case, an associated constant, or an immutable global; anything else is an error, never a variable of that name.

Constant Patterns

A pattern identifier that resolves to an immutable global or an associated constant matches by value, instead of binding a new variable:

global TK_FOO: i32 = 1;
global TK_BAR: i32 = 2;

fn token_kind(token: i32) -> String {
    return match token {
        TK_FOO | TK_BAR => "keyword",
        i32::MAX        => "max",
        _               => "other",
    };
}

test {
    assert token_kind(2) == "keyword" && token_kind(i32::MAX) == "max";
    assert token_kind(3) == "other";
}

The pattern matches where scrutinee == CONSTANT holds, so a constant of any type with an Eq compares as == would: a String, a struct, a tuple or an Option constant matches at the top of an arm or nested in another pattern.

A constant is not a pattern when its type is a float or holds one, in a field, element or payload at any depth. So f64::INFINITY, a global holding 0.1, and a struct constant with an f64 field are errors here. A float constant is still a range bound.

Only a refutable pattern reads a bare name as a constant: a match arm, if let, while let, and let ... else. So let limit = v else { … } runs the else block unless v == limit. A local, a parameter, or a closure capture that takes the name puts the global out of reach, so the name binds there as it would anywhere else. What counts is what is in scope where the pattern starts. A name the pattern binds itself reaches none of its own sites, so [mut limit, limit] binds the first element and tests the second against the global.

Range Patterns

A range pattern matches a value between its bounds. It stands wherever a refutable pattern may, nested ones included:

let grade = match score {
    0..<60 => "F",
    60..=100 => "P",
    _ => "invalid",
};
let lower = c matches { 'a'..='z' };
assert grade == "P" && lower;

The compiler knows the value of a literal and of a primitive type's limit, such as i32::MAX or f64::INFINITY. A range bounded by those alone is checked before the program runs:

Any other constant shows its value only when the match runs. A range bounded by one matches where START <= scrutinee and scrutinee < END (<= for ..=) hold by the type's order, as a constant pattern matches where == holds. It covers no value for exhaustiveness:

let score = builtin::black_box(15);
let tier = match score {
    LOW..<HIGH => 1,
    HIGH..=Grade::PASS => 2,
    _ => 0,
};
assert tier == 1;

A float range compares by the float order, so 0.0..<1.0 matches -0.0:

let zero = builtin::black_box(-0.0);
assert zero matches { 0.0..<1.0 };
let nan = builtin::black_box(f64::NAN);
assert !(nan matches { f64::NEG_INFINITY..=f64::INFINITY });

Two more rules apply to one the compiler checks:

The range operators themselves are in Ranges.

Or Patterns

Or patterns match if any alternative matches. All alternatives must bind the same names with the same types:

// Enum or-patterns
let tone = match color {
    Red | Blue => "cool",
    Green => "warm",
};
assert tone == "cool";

// Variant or-patterns with bindings
let value = match expr {
    Num(n) | Neg(n) => n,
    Zero => 0,
};
assert value == 7;

// Literal or-patterns
let level = match count {
    1 | 2 | 3 => "low",
    _ => "high",
};
assert level == "low";

// Or patterns in matches operator
assert shape matches { Circle(_) | Square(_) };

Nested Sub-Patterns

A tuple or struct pattern may hold any refutable pattern in an element or field: a literal, a case, an or-pattern, or a range.

// Literal sub-patterns in tuples
let axis = match [dx, dy] {
    [0, 0] => "origin",
    [0, _] => "y-axis",
    [_, 0] => "x-axis",
    _ => "other",
};
assert axis == "y-axis";

// Variant sub-patterns in tuples
let sum = match [a, b] {
    [Some(p), Some(q)] => p + q,
    [Some(p), None] => p,
    [None, _] => 0,
};
assert sum == 3;

// Literal sub-patterns in structs
let point = Pt { x: 0, y: 0 };
let mut origin = false;
if let { x: 0, y: 0 } = point { origin = true; }
assert origin;

// Enum sub-patterns in tuples
let label = match [color, size] {
    [Red, Large] => "big red",
    [Blue, _] => "blue",
    _ => "other",
};
assert label == "big red";

// Or-pattern and range sub-patterns
let p = Pt { x: 15, y: 4 };
let v = match p {
    { x: 0 | 1, y } => y,
    { x: 10..=20, y } => -y,
    _ => 0,
};
assert v == -4;

Mutable Bindings in Patterns

mut before a binding name makes that binding mutable:

if let Some(mut x) = opt {
    x += 10;  // x is mutable
    assert x == 11;
}

let doubled = match result {
    Ok(mut value) => {
        value *= 2;
        value
    },
    Err(_) => 0,
};
assert doubled == 42;

Match Ergonomics

When the scrutinee of if let, match, or matches is a reference (&T or &mut T), patterns match against the type it points to, and payload bindings become references. Matching &Option<T> with Some(x) gives x: &T, not x: T, as Rust's match ergonomics (RFC 2005) do.

Under a &mut scrutinee, a payload of a replace-on-assign type (a primitive, enum, flags or fn) cannot bind at all, even to be read: matching &mut Option<i32> with Some(x) is an error. Match *r to bind the payload by value, or a & to the value to bind a &i32. See Mutable References to Fields and Elements.

A destructuring let or for binding follows the same rule, and so does a reference met below the top of a pattern: for let [a, b] of &pairs gives a: &A, and [n, { x, .. }] against [i32, &Point] gives x: &i32.

let opt: Option<i32> = Option::<i32>::Some(42);
let ro = &opt;
if let Some(x) = ro {       // ro: &Option<i32>, x: &i32
    assert *x == 42;        // dereference to use the value
}

Type Patterns

A pattern may ascribe a type: p: T matches when the subject is a T, and p binds it. The ascription on a let is this pattern, so one rule covers both spellings.

Whether the pattern can fail is decided statically, from the subject's type S:

Relation Meaning
S <: T irrefutable: an upcast, or an ordinary type annotation
T <: S, T ≠ S refutable: a runtime test, and only where extends relates the two
otherwise a type error, as a mismatched annotation is

An irrefutable ascription still drives type context, so let x: i64 = 42 coerces the literal. A refutable one needs a pattern position that admits failure, so let and a for binding reject it exactly as they reject Some(x):

fn check(el: Element, node: Node) {
    let n: Node = el;                                   // Element <: Node — irrefutable upcast
    if let input: HtmlInputElement = el { assert input == n; }
    if node matches { _: Element } { assert kind(node) == "element"; }  // the predicate form
    let input: HtmlInputElement = el else { return; };  // the guard form
    assert kind(input) == "input";
}

fn kind(node: Node) -> String {
    return match node {
        input: HtmlInputElement => "input",
        elem: Element => "element",
        _ => "other",                                   // required: the hierarchy is open
    };
}

A refutable ascription in a plain let is rejected:

let input: HtmlInputElement = el;                   // ERROR: refutable pattern in `let`

A type match over resources always needs a final _ arm, because the host may hand back a type the program does not name. An unguarded arm whose type is a supertype of a later arm's makes that later arm unreachable, which is an error, as any unreachable arm is.

A refutable ascription tests a handle, so it binds a name or _ and nothing deeper, and its subject is the value rather than a reference to it. T must be a concrete type: a type parameter says nothing about whether it narrows.