Match
match compares an expression against patterns, top to bottom, and runs the
first one that matches. Like if, it is an expression: the chosen arm is the
value of the match. The compiler requires the cases to be exhaustive.
The simplest form matches by literal value — clearer than a chain of
if/else if when every branch compares the same expression:
A dispatch table by number and by string; _ is the default case.
// Feature: `match` with literals
// Syntax: `match expr { lit1 => arm1, lit2 => arm2, _ => default }`
// When to use: dispatch table by concrete value
// (numbers, strings, bools). Clearer than a chain of
// `if/else if` when every branch compares the same expression.
let x = 3
let name = match x {
1 => "one",
2 => "two",
3 => "three",
_ => "other",
}
print("x = {name}") // x = three
// Match with strings.
let day = "tue"
let lng = match day {
"mon" => "Monday",
"tue" => "Tuesday",
"wed" => "Wednesday",
"thu" => "Thursday",
"fri" => "Friday",
_ => "weekend",
}
print(lng) // Tuesday
// Match returns a value — it's an expression.
fn http_label(code: int) -> str {
return match code {
200 => "OK",
404 => "Not Found",
500 => "Server Error",
_ => "Unknown",
}
}
print(http_label(200)) // OK
print(http_label(404)) // Not Found
print(http_label(418)) // Unknown
Several values on the same arm use the or pattern (a | b):
a | b => … covers multiple values with a single arm.
// Feature: `match` with or-patterns
// Syntax: `pat1 | pat2 | pat3 => arm`
// When to use: group several cases that share the same
// body. Avoids repeating "=> ..." multiple times.
let day = "Saturday"
let kind = match day {
"Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" => "weekday",
"Saturday" | "Sunday" => "weekend",
_ => "unknown",
}
print("{day} is {kind}") // Saturday is weekend
// Or-patterns with integers.
fn small_prime(n: int) -> bool {
return match n {
2 | 3 | 5 | 7 | 11 | 13 => true,
_ => false,
}
}
print(small_prime(7)) // true
print(small_prime(8)) // false
print(small_prime(13)) // true
// Combining many cases in one concise expression.
let ch = "u"
let kind2 = match ch {
"a" | "e" | "i" | "o" | "u" => "vowel",
_ => "consonant or other",
}
print(kind2) // vowel
Ranges match contiguous intervals:
1..=5 => … matches any value in the range.
// Feature: `match` with ranges
// Syntax: `start..=end => arm` (inclusive)
// `start..end => arm` (exclusive)
// When to use: classify a numeric value into continuous
// bands — age, score, time-of-day window, etc.
let age = 25
let group = match age {
0..=12 => "child",
13..=19 => "teenager",
20..=64 => "adult",
_ => "senior",
}
print("{age} -> {group}") // 25 -> adult
// Score / report card.
let score = 85
let grade = match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
print("{score} = {grade}") // 85 = B
// Combination of literal + range.
let h = 14
let period = match h {
0..=5 => "late night",
6..=11 => "morning",
12 => "noon",
13..=17 => "afternoon",
18..=23 => "evening",
_ => "?",
}
print("{h}h -> {period}") // 14h -> afternoon
A guard adds an extra condition to the arm:
x if x > 0 => … only matches when the guard is true.
// Feature: `match` with guards
// Syntax: `pattern if cond => arm`
// When to use: refine a pattern with an arbitrary
// condition. Runs only if the pattern matches AND the
// condition is true. Replaces ranges/literals when the
// rule is dynamic.
let n = -5
let descr = match n {
x if x < 0 => "negative: {x}",
0 => "zero",
x if x < 10 => "small positive: {x}",
x => "large: {x}",
}
print(descr) // negative: -5
// Guards using other variables in scope.
let limit = 100
let v = 250
let result = match v {
x if x <= limit => "ok ({x})",
x => "overflow ({x})",
}
print(result) // overflow (250)
// Combining a guard with a range pattern.
let score = 87
let level = match score {
s if s == 100 => "perfect",
90..=99 => "excellent",
s if s >= 70 => "ok ({s})",
_ => "poor",
}
print(level) // ok (87)
match also destructures structs and enums, binding the fields:
Extracts struct fields right in the pattern.
// Feature: `match` with struct destructuring
// Syntax: `match s { Type { field1, field2, .. } => ... }`
// When to use: extract fields from a struct while
// branching. `..` ignores extra fields.
struct Point {
x: float,
y: float,
z: float,
}
let p = Point { x: 0.0, y: 0.0, z: 5.0 }
let location = match p {
Point { x: 0.0, y: 0.0, z: 0.0 } => "origin",
Point { x, y: 0.0, z: 0.0 } => "X axis at {x}",
Point { x: 0.0, y, z: 0.0 } => "Y axis at {y}",
Point { x: 0.0, y: 0.0, z } => "Z axis at {z}",
Point { x, y, z } => "point ({x},{y},{z})",
}
print(location) // Z axis at 5
// Pick only the fields you need with `..`.
struct User {
name: str,
age: int,
active: bool,
}
let u = User { name: "Alice", age: 30, active: true }
let label = match u {
User { name, .. } => "hello {name}",
}
print(label) // hello Alice
Each enum variant becomes an arm — exhaustive by construction.
// Feature: `match` with enum destructuring
// Syntax: `match val { EnumName::Variant(payload) => ... }`
// When to use: cover every variant of an enum
// (sum-type / tagged union). The compiler verifies that
// every case is handled (exhaustive).
enum Shape {
Circle(float),
Rectangle(float, float),
Triangle(float, float, float),
}
fn area(s: Shape) -> float {
return match s {
Shape::Circle(r) => 3.14159 * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(a, b, c) => {
// Heron's formula.
let s = (a + b + c) / 2.0
return s
},
}
}
print(area(Shape.Circle(2.0))) // ~12.566
print(area(Shape.Rectangle(3.0, 4.0))) // 12
// Variant without payload + variant with payload in the same enum.
enum Maybe {
Yes,
No,
Custom(str),
}
fn say(m: Maybe) -> str {
return match m {
Maybe::Yes => "yes",
Maybe::No => "no",
Maybe::Custom(txt) => "maybe: {txt}",
}
}
print(say(Maybe.Yes)) // yes
print(say(Maybe.No)) // no
print(say(Maybe.Custom("depends"))) // maybe: depends
And patterns can be nested (enum inside enum, arrays):
// Feature: `match` with nested patterns
// Syntax: patterns inside patterns
// When to use: match composite structures (enum inside
// enum, struct inside array, etc.) without doing it
// in two stages.
use std::Result
enum Result {
Ok(int),
Err(str),
}
enum Step {
Start,
Done(Result),
}
fn descr(s: Step) -> str {
return match s {
Step::Start => "starting",
Step::Done(Result::Ok(n)) => "ok with {n}",
Step::Done(Result::Err(msg)) => "error: {msg}",
}
}
print(descr(Step.Start)) // starting
print(descr(Step.Done(Result.Ok(42)))) // ok with 42
print(descr(Step.Done(Result.Err("timeout")))) // error: timeout
// Nested pattern in arrays.
let pairs = [[1, 2], [0, 0], [3, 4]]
for p in pairs {
let m = match p {
[0, 0] => "origin",
[x, 0] => "X axis at {x}",
[0, y] => "Y axis at {y}",
[x, y] => "({x}, {y})",
_ => "?",
}
print(m)
}
// expected:
// (1, 2)
// origin
// (3, 4)
Challenge
In the literals example, replace _ with a range 400..=499 => "client error"
and run it. What happens to exhaustiveness?