Skip to content

Pattern matching

A pattern tests a value’s shape and names the parts inside it.

match runs the first arm whose pattern fits the value, and evaluates to what that arm returns.

fn describe(n: int) -> string {
match n {
0 => "zero",
1 => "one",
_ => "many",
}
}

Patterns must cover all possible values:

enum Color { Red, Green, Blue }
match color {
Color.Red => "red",
Color.Green => "green",
}

The compiler enforces exhaustiveness:

Terminal window
match is not exhaustive
╭─[example.lis:4:3]
3 │ fn name(color: Color) -> string {
4match color {
· ─────┬─────
· ╰── not all patterns covered
5 │ Color.Red => "red",
╰────
help: Handle the missing case Color.Blue, e.g. Color.Blue => { ... } · code: [infer.non_exhaustive]

Integer, boolean, string, and rune literals match exact values:

match status {
"ok" => 200,
"missing" => 404,
_ => 500,
}
match letter {
'a' => "letter a",
'b' => "letter b",
_ => "other",
}

An identifier binds the matched value to a name:

match opt {
Some(n) => n * 2,n is the value inside Some
None => 0,
}

_ matches any value without binding:

match opt {
Some(_) => "has value",_ discards the value inside Some
None => "empty",
}

Tuple patterns destructure by position:

let pair = (10, 20)
match pair {
(0, 0) => "origin",
(x, 0) => f"on x-axis at {x}",
(0, y) => f"on y-axis at {y}",
(x, y) => f"at ({x}, {y})",
}

Struct patterns match fields by name:

struct Point {
x: int,
y: int,
}
let point = Point { x: 10, y: 0 }
match point {
Point { x: 0, y: 0 } => "origin",
Point { x, y: 0 } => f"on x-axis at {x}",
Point { x, y } => f"at ({x}, {y})",
}

Use .. to ignore remaining fields:

struct User {
name: string,
email: string,
age: int,
}
let user = User {
name: "Alice",
email: "alice@example.com",
age: 30,
}
match user {
User { name: "", .. } => "hello, stranger",
User { name, .. } => f"hello, {name}",
.. ignores email and age
}

Enum patterns match variants and destructure their payloads:

enum Message {
Ready,
Write(string),
Move { x: int, y: int },
}
let msg = Message.Write("hello")
match msg {
Message.Ready => "ready",
Message.Write(text) => f"writing: {text}",
Message.Move { x, y } => f"moving to ({x}, {y})",
}

Inside a match arm, the enum qualifier can be omitted:

match msg {
Ready => "ready",
Message. qualifier omitted
Write(text) => f"writing: {text}",
Move { x, y } => f"moving to ({x}, {y})",
}

Bracketed patterns match slice and array elements:

let items = [1, 2, 3]
match items {
[] => "empty",
[n] => f"single: {n}",
[first, second] => f"pair: {first}, {second}",
[first, ..rest] => f"first is {first}, {rest.length()} more",
rest is [2, 3]
}

The rest pattern ..rest captures remaining elements as a Slice when matching a slice, or as an Array when matching an array. It must appear last. Elements after .. are not allowed.

Use .. without an identifier to ignore the rest:

match items {
[first, ..] => first,
.. discards all remaining elements
[] => 0,
}

Use | to match multiple patterns in one arm:

match direction {
North | South => "north-south",
East | West => "east-west",
}

Alternatives can bind variables if all of them bind the same names:

enum Event {
KeyDown(rune),
KeyUp(rune),
}
match event {
KeyDown(key) | KeyUp(key) => f"key: {key}",
key is rune
}

Use as to capture the entire matched value:

let mut history = Slice.new<Message>()
match msg {
Ready => "ready",
Write(text) => text,
Move { x, .. } as moved => {
moved is Move itself
history = history.append(moved)
f"moved to {x}"
}
}

For an arm with alternatives, place as on each one:

match event {
KeyDown(key) as pressed | KeyUp(key) as pressed => record(pressed, key),
}

Add if after a pattern to require an additional condition:

match opt {
Some(n) if n > 0 => "positive",
Some(_) => "non-positive",
None => "empty",
}

A guard can use a value captured with as:

match opt {
Some(Point { x, .. }) as point if x > 0 => transform(point),
_ => default,
}

Guards do not count toward exhaustiveness. If all arms have guards, a wildcard or catch-all arm is still required.

A plain let needs a pattern that always matches. Some(n) might not, so use let else. The else branch runs when the match fails, and must return, break, or continue.

fn double_or_zero(opt: Option<int>) -> int {
let Some(n) = opt else { return 0 }
n * 2
}

A slice pattern that constrains the length can fail, because a slice has no fixed length:

fn first_two(items: Slice<int>) -> int {
let [first, second, ..] = items else { return 0 }
first + second
}