Pattern Matching in Rust
Patterns are a special syntax in Rust for matching against the structure of types.
A pattern describes a shape — and when a value fits that shape, the match succeeds and
you can optionally bind pieces of the value to names. Patterns appear in match
arms, if let, while let, for loops, let statements, and function parameters.
Together they form one of the most expressive and safe parts of the language.
Where Patterns Are Used
Patterns are not limited to match — they appear in several places across Rust.
matcharms — the primary home of patterns; every arm is a patternif let— a concise alternative when you only care about one variantwhile let— loop as long as a pattern keeps matchingforloops — destructure each item of an iterator in placeletstatements — everylet x = ...is a pattern (x binds the whole value)Function parameters — destructure arguments at the call site
// match arm
match some_value {
Some(x) => println!("got {}", x),
None => println!("nothing"),
}
// if let — fires only when the pattern matches
if let Some(x) = some_value {
println!("got {}", x);
}
// while let — loop until the pattern stops matching
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top);
}
// for loop — destructure each tuple element
let pairs = vec![(0, 'a'), (1, 'b'), (2, 'c')];
for (index, letter) in &pairs {
println!("{}: {}", index, letter);
}
// let statement — the whole left side is a pattern
let (x, y, z) = (1, 2, 3);
// function parameter destructuring
fn print_pair(&(first, second): &(i32, i32)) {
println!("({}, {})", first, second);
}Literal Patterns
The simplest pattern is an exact literal value. The pattern matches only if the scrutinee equals that value precisely. Literals can be integers, characters, booleans, or string slices.
fn classify(n: i32) -> &'static str {
match n {
0 => "zero",
1 => "one",
2 => "two",
-1 => "negative one",
_ => "something else",
}
}
fn describe_bool(b: bool) -> &'static str {
match b {
true => "yes",
false => "no",
}
}
fn vowel_check(c: char) -> &'static str {
match c {
'a' | 'e' | 'i' | 'o' | 'u' => "lowercase vowel",
'A' | 'E' | 'I' | 'O' | 'U' => "uppercase vowel",
_ => "consonant or other",
}
}
fn main() {
println!("{}", classify(0)); // zero
println!("{}", classify(2)); // two
println!("{}", classify(99)); // something else
println!("{}", describe_bool(true)); // yes
println!("{}", vowel_check('e')); // lowercase vowel
}Variable (Binding) Patterns
A plain identifier in a pattern position is a variable pattern. It matches
anything and binds the matched value to that name. This is what every plain
let x = ... statement uses — x is a variable pattern that matches any value.
fn main() {
let number = 42;
match number {
// 'n' is a variable pattern — binds whatever 'number' is
n => println!("The number is {}", n),
}
// Variable patterns in let statements
let x = 10; // x binds 10
let (a, b) = (1, 2); // a binds 1, b binds 2
println!("x={} a={} b={}", x, a, b);
// The difference between a variable pattern and wildcard:
let value = String::from("hello");
match value {
// 's' takes ownership — value is moved into s
s => println!("got: {}", s),
// '_' would NOT move ownership
}
}The Wildcard Pattern _
The underscore _ is the wildcard pattern. It matches any value but does not
bind it to a name, and crucially it does not take ownership. Use it when you want
to discard a value intentionally.
fn main() {
let pair = (1, 99);
match pair {
(0, _) => println!("first is zero, second ignored"),
(_, 0) => println!("second is zero, first ignored"),
(x, _) => println!("first is {}, second ignored", x),
}
// first is 1, second ignored
// Ignore an entire value in a for loop
for _ in 0..3 {
println!("tick");
}
// tick (x3)
// Silence unused-variable warnings during development
let _in_progress = setup_something();
}
fn setup_something() -> i32 { 0 }Range Patterns
Use the inclusive range pattern ..= to match any value within a span. Range
patterns work with integer and character literals. Exclusive .. ranges are not
supported inside patterns — only the inclusive ..= form is allowed.
fn grade(score: u32) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
0..=59 => "F",
_ => "invalid score",
}
}
fn char_kind(c: char) -> &'static str {
match c {
'a'..='z' => "lowercase letter",
'A'..='Z' => "uppercase letter",
'0'..='9' => "digit",
_ => "other character",
}
}
fn main() {
println!("{}", grade(97)); // A
println!("{}", grade(73)); // C
println!("{}", grade(40)); // F
println!("{}", char_kind('m')); // lowercase letter
println!("{}", char_kind('7')); // digit
println!("{}", char_kind('!')); // other character
}Multiple Patterns with |
The pipe | separates multiple patterns in a single arm. The arm fires if the
scrutinee matches any one of them. You can mix literals, ranges, and | freely
within one arm.
fn main() {
let day: u8 = 6; // 1=Mon … 7=Sun
let kind = match day {
1 | 2 | 3 | 4 | 5 => "weekday",
6 | 7 => "weekend",
_ => "invalid day",
};
println!("{}", kind); // weekend
// Mix ranges and literals in the same arm
let n: i32 = 15;
match n {
0 => println!("zero"),
1 | 2 | 3 => println!("one, two, or three"),
4..=10 => println!("four through ten"),
11..=20 => println!("eleven through twenty"),
_ => println!("out of handled range"),
}
// eleven through twenty
// Useful for HTTP status codes
let status = 403u16;
match status {
200 | 201 | 204 => println!("success"),
301 | 302 => println!("redirect"),
400 | 401 | 403 | 404 => println!("client error"),
500..=599 => println!("server error"),
_ => println!("other"),
}
// client error
}Tuple Patterns
Tuples can be matched element by element. You can mix literals, variable bindings, and wildcards within a tuple pattern to match only the structure you care about.
fn classify_point(p: (i32, i32)) -> &'static str {
match p {
(0, 0) => "origin",
(x, 0) => "on the x-axis", // 0 must match second element; x is bound
(0, y) => "on the y-axis", // 0 must match first element; y is bound
_ => "somewhere in the plane",
}
}
fn describe_rgb(color: (u8, u8, u8)) -> &'static str {
match color {
(255, 0, 0 ) => "pure red",
(0, 255, 0 ) => "pure green",
(0, 0, 255) => "pure blue",
(r, g, b ) if r == g && g == b => "shade of grey",
_ => "mixed color",
}
}
fn main() {
println!("{}", classify_point((0, 0))); // origin
println!("{}", classify_point((5, 0))); // on the x-axis
println!("{}", classify_point((0, 3))); // on the y-axis
println!("{}", classify_point((2, 7))); // somewhere in the plane
println!("{}", describe_rgb((255, 0, 0))); // pure red
println!("{}", describe_rgb((128, 128, 128))); // shade of grey
println!("{}", describe_rgb((10, 20, 30))); // mixed color
}Struct Patterns
Struct patterns destructure a struct, binding its fields to variable names. You
can match specific field values while binding others, and use .. to skip fields
you do not need. Field shorthand works: Point { x, y } binds field x to the
name x and field y to the name y.
#[derive(Debug)]
struct Point { x: i32, y: i32 }
fn describe_point(p: &Point) -> String {
match p {
Point { x: 0, y: 0 } => String::from("at the origin"),
Point { x: 0, y } => format!("on the y-axis at y={}", y),
Point { x, y: 0 } => format!("on the x-axis at x={}", x),
Point { x, y } => format!("at ({}, {})", x, y),
}
}
#[derive(Debug)]
struct Rectangle {
top_left: Point,
bottom_right: Point,
label: String,
}
fn describe_rect(r: &Rectangle) {
// Nested struct destructuring; ignore 'label' with ..
let Rectangle {
top_left: Point { x: x1, y: y1 },
bottom_right: Point { x: x2, y: y2 },
..
} = r;
println!("rect from ({},{}) to ({},{})", x1, y1, x2, y2);
}
fn main() {
println!("{}", describe_point(&Point { x: 0, y: 5 })); // on the y-axis at y=5
println!("{}", describe_point(&Point { x: 3, y: 0 })); // on the x-axis at x=3
println!("{}", describe_point(&Point { x: 2, y: 4 })); // at (2, 4)
let r = Rectangle {
top_left: Point { x: 1, y: 10 },
bottom_right: Point { x: 8, y: 2 },
label: String::from("main rect"),
};
describe_rect(&r); // rect from (1,10) to (8,2)
}Enum Patterns
Enum patterns are where Rust's pattern matching truly shines. Each variant is a distinct pattern. Tuple-style variants destructure positionally; struct-style variants destructure by field name; unit variants are matched by name alone.
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
fn process(msg: Message) {
match msg {
Message::Quit =>
println!("Quit message received"),
Message::Move { x, y } =>
println!("Move to ({}, {})", x, y),
Message::Write(text) =>
println!("Write: {}", text),
Message::ChangeColor(r, g, b) =>
println!("Color: rgb({}, {}, {})", r, g, b),
}
}
fn main() {
process(Message::Quit);
process(Message::Move { x: 10, y: 20 });
process(Message::Write(String::from("hello")));
process(Message::ChangeColor(255, 128, 0));
}
// Quit message received
// Move to (10, 20)
// Write: hello
// Color: rgb(255, 128, 0)// Option and Result are enums — same pattern syntax applies
fn safe_divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
fn main() {
match safe_divide(10.0, 2.0) {
Some(result) => println!("result: {}", result), // result: 5
None => println!("division by zero"),
}
// Nested enum patterns: Option<Result<...>>
let nested: Option<Result<i32, &str>> = Some(Ok(42));
match nested {
Some(Ok(n)) => println!("success: {}", n), // success: 42
Some(Err(e)) => println!("inner error: {}", e),
None => println!("no value"),
}
}Match Guards
A match guard adds an extra if condition after a pattern. The arm fires only
if the pattern matches AND the guard condition is true. Guards let you express
conditions that patterns alone cannot capture.
fn main() {
let pair = (4, -4);
match pair {
(x, y) if x == y => println!("equal"),
(x, y) if x + y == 0 => println!("opposite signs: {} and {}", x, y),
(x, _) if x % 2 == 0 => println!("first is even: {}", x),
(x, y) => println!("other: ({}, {})", x, y),
}
// opposite signs: 4 and -4
// Guards combine naturally with enum patterns
let numbers = vec![Some(5), None, Some(-3), Some(100), None];
for opt in &numbers {
match opt {
Some(n) if *n > 0 && *n < 50 => println!("{} is small positive", n),
Some(n) if *n >= 50 => println!("{} is large positive", n),
Some(n) => println!("{} is non-positive", n),
None => println!("no value"),
}
}
// 5 is small positive
// no value
// -3 is non-positive
// 100 is large positive
// no value
}@ Bindings — Capture and Test at Once
The @ operator lets you simultaneously test a value against a pattern AND bind
it to a name. Without @, you must choose: either test a range (and lose the
actual value) or bind with a variable (and lose the test). The @ operator gives
you both.
fn describe_number(n: i32) -> String {
match n {
// Bind to 'small' AND verify it falls in 1..=5
small @ 1..=5 => format!("{} is a small positive number", small),
// Bind to 'mid' AND verify it falls in 6..=20
mid @ 6..=20 => format!("{} is a medium number", mid),
other => format!("{} is outside the handled range", other),
}
}
fn main() {
println!("{}", describe_number(3)); // 3 is a small positive number
println!("{}", describe_number(15)); // 15 is a medium number
println!("{}", describe_number(99)); // 99 is outside the handled range
// @ inside a struct pattern
struct Sensor { id: u32, reading: f64 }
let s = Sensor { id: 7, reading: 98.6 };
match s {
Sensor { id: n @ 1..=10, reading } =>
println!("sensor {} reading: {:.1}", n, reading),
Sensor { id, .. } =>
println!("unknown sensor {}", id),
}
// sensor 7 reading: 98.6
// @ with Option patterns
let msg: Option<u32> = Some(42);
match msg {
Some(n @ 1..=100) => println!("in range: {}", n), // in range: 42
Some(n) => println!("out of range: {}", n),
None => println!("nothing"),
}
}Nested Patterns
Patterns can be nested to arbitrary depth, letting you match the structure of complex, layered data in a single expression without unpacking manually.
#[derive(Debug)]
enum Expr {
Num(f64),
Add(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
}
fn eval(expr: &Expr) -> f64 {
match expr {
Expr::Num(n) => *n,
Expr::Add(a, b) => eval(a) + eval(b),
Expr::Mul(a, b) => eval(a) * eval(b),
}
}
fn main() {
// (2 + 3) * 4
let expr = Expr::Mul(
Box::new(Expr::Add(
Box::new(Expr::Num(2.0)),
Box::new(Expr::Num(3.0)),
)),
Box::new(Expr::Num(4.0)),
);
println!("{}", eval(&expr)); // 20
// Nested Option matching
let nested: Option<Option<i32>> = Some(Some(7));
match nested {
Some(Some(n)) if n > 0 => println!("positive inner value: {}", n),
Some(Some(n)) => println!("non-positive inner value: {}", n),
Some(None) => println!("outer Some wraps None"),
None => println!("outer None"),
}
// positive inner value: 7
}Irrefutable vs Refutable Patterns
Rust distinguishes two categories of patterns:
- Irrefutable — always match, regardless of the value (e.g.
x,(a, b)). Required inletstatements, function parameters, andforloops. - Refutable — might not match (e.g.
Some(x)does not matchNone). Used inif let,while let, andmatcharms.
Placing a refutable pattern where an irrefutable one is required is a compile error.
fn main() {
// Irrefutable: (x, y) always matches any 2-tuple — OK in let
let (x, y) = (1, 2);
println!("{} {}", x, y);
// ERROR — refutable pattern in let statement:
// let Some(n) = Some(5); // compiler: "pattern is refutable"
// CORRECT: use if let for refutable patterns
let opt: Option<i32> = Some(10);
if let Some(n) = opt {
println!("got {}", n); // got 10
}
// while let: keep looping while the pattern matches
let mut optional = Some(0u32);
while let Some(n) = optional {
println!("n = {}", n);
optional = if n < 3 { Some(n + 1) } else { None };
}
// n = 0
// n = 1
// n = 2
// n = 3
}.. to Ignore Remaining Fields or Elements
Use .. in a struct or tuple pattern to ignore the fields or elements you do not
need. This is especially useful for large structs — it prevents your patterns from
breaking every time a new field is added.
struct Config {
host: String,
port: u16,
timeout: u32,
retries: u8,
debug: bool,
}
fn connect(cfg: &Config) {
// Only care about host and port; ignore everything else with ..
let Config { host, port, .. } = cfg;
println!("Connecting to {}:{}", host, port);
}
fn main() {
let cfg = Config {
host: String::from("localhost"),
port: 8080,
timeout: 30,
retries: 3,
debug: false,
};
connect(&cfg); // Connecting to localhost:8080
// .. in a tuple pattern — ignore middle elements
let numbers = (1, 2, 3, 4, 5);
let (first, .., last) = numbers;
println!("first={} last={}", first, last); // first=1 last=5
// .. in a tuple struct
struct Rgb(u8, u8, u8);
let color = Rgb(255, 128, 0);
let Rgb(r, ..) = color;
println!("red channel: {}", r); // red channel: 255
}Pattern Matching Quick Reference
Pattern type | Syntax example | What it does |
|---|---|---|
Literal |
| Matches exactly that value |
Variable |
| Matches anything and binds to the name |
Wildcard |
| Matches anything; no binding, no ownership transfer |
Range |
| Matches values within the inclusive range |
Multiple |
| Matches any of the listed patterns |
Tuple |
| Matches tuple structure element by element |
Struct |
| Destructures struct fields by name |
Enum variant |
| Matches a specific enum variant and its data |
Guard |
| Adds an extra boolean condition after the pattern |
@ binding |
| Tests the pattern and binds the value simultaneously |
Ignore rest |
| Skips unspecified fields or tuple elements |