Rust Pattern Matching Cheat Sheet
Covers match expressions, if let and while let, destructuring structs and enums, match guards, and binding patterns in Rust.
match Basics
Exhaustive matching over values, ranges, and enum variants.
fn describe(n: i32) -> &'static str { match n { 0 => "zero", 1 | 2 | 3 => "small", // multiple patterns 4..=9 => "medium", // inclusive range n if n < 0 => "negative", // match guard _ => "large", // catch-all }}enum Coin { Penny, Nickel, Dime, Quarter(String), // state name}fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("Quarter from {}!", state); 25 } }}
Destructuring
Pulling apart structs, tuples, and enums into named bindings.
struct Point { x: i32, y: i32 }let p = Point { x: 0, y: 7 };let Point { x, y } = p;println!("{} {}", x, y);// Destructuring in match, with nested enumsenum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 },}fn area(shape: &Shape) -> f64 { match shape { Shape::Circle { radius } => std::f64::consts::PI * radius * radius, Shape::Rectangle { width, height } => width * height, }}// Tuple destructuringlet (a, b, c) = (1, 2, 3);// Ignoring parts with `..`let Point { x, .. } = Point { x: 1, y: 2 };
Match Guards & @ Bindings
Adding extra conditions and capturing matched values by name.
let pair = (2, -2);match pair { (x, y) if x + y == 0 => println!("These sum to zero"), (x, _) if x % 2 == 0 => println!("First is even"), _ => println!("No match"),}// @ bindings: test a value AND bind it to a namelet msg_id = 5;match msg_id { id @ 3..=7 => println!("Found id in range: {}", id), _ => println!("Out of range"),}
Pattern Syntax Reference
Symbols used across match arms and let/if let bindings.
- _- Wildcard; matches anything without binding a value
- ..- Ignores remaining fields/elements in a struct, tuple, or slice pattern
- |- Or-pattern; matches if any of the listed patterns match
- a..=b- Inclusive range pattern (works for char and numeric types)
- name @ pattern- Binds the matched value to `name` while also testing it against `pattern`
- ref / ref mut- Binds by reference instead of moving the matched value (rarely needed post-NLL)
- Some(x) / None- Standard destructuring of Option in match arms
if let / while let
Concise matching for a single pattern of interest.
let config_max: Option<u8> = Some(3);// if let: handle one pattern, ignore the restif let Some(max) = config_max { println!("Max is {}", max);} else { println!("No max set");}// while let: loop as long as the pattern keeps matchinglet mut stack = vec![1, 2, 3];while let Some(top) = stack.pop() { println!("{}", top);}// let-else (Rust 1.65+): bind or divergelet Some(max) = config_max else { panic!("config_max should be set");};
Slice & Array Patterns
Destructuring the head, tail, and fixed positions of slices and arrays.
fn describe(nums: &[i32]) -> String { match nums { [] => "empty".to_string(), [x] => format!("single: {x}"), [first, .., last] => format!("first {first}, last {last}"), [a, b, rest @ ..] => format!("a={a} b={b} rest_len={}", rest.len()), }}// Fixed-size array patterns must match the exact lengthlet point3 = [1, 2, 3];if let [x, y, z] = point3 { println!("{x} {y} {z}");}// `rest @ ..` binds the remaining elements as a sub-slicelet log = ["INFO", "start", "ok"];if let [level, rest @ ..] = log { println!("level={level} rest={:?}", rest);}
Match Ergonomics & Nested References
How the compiler auto-derefs and auto-refs patterns against &T without explicit ref/*.
struct Wrapper { value: Option<Box<i32>> }fn inspect(w: &Wrapper) { // Pre-2018 you'd need `match &w.value { Some(ref b) => ... }`. // Match ergonomics let you write this directly against a reference: match &w.value { Some(boxed) => println!("got {}", **boxed), // boxed: &Box<i32> None => println!("empty"), }}// Deeply nested Option<Result<T, E>> matching without manual derefsfn handle(input: &Option<Result<i32, String>>) { match input { Some(Ok(n)) if *n > 0 => println!("positive: {n}"), Some(Ok(n)) => println!("non-positive: {n}"), Some(Err(e)) => println!("error: {e}"), None => println!("nothing"), }}
Or-Patterns in Let, Function Args, and Nested Positions
The `|` pattern combinator works beyond match arms, including inside other patterns.
enum Shape { Circle(f64), Square(f64), Triangle(f64, f64, f64) }// Or-patterns nested inside a tuple patternfn classify((a, b): (i32, i32)) -> &'static str { match (a, b) { (0, _) | (_, 0) => "has a zero", (x, y) if x == y => "equal", _ => "other", }}// `matches!` combined with or-patterns avoids a full match blockfn is_regular(s: &Shape) -> bool { matches!(s, Shape::Circle(_) | Shape::Square(_))}// Or-patterns work in `if let` and `while let` too (since Rust 2021)fn is_edge_key(k: &str) -> bool { matches!(k, "esc" | "tab" | "enter")}
Matching Trait Objects & Downcasting
Pattern matching doesn't work directly on `dyn Trait`; use Any + downcast_ref to recover the concrete type first.
use std::any::Any;trait Event: Any { fn as_any(&self) -> &dyn Any;}struct Click { x: i32, y: i32 }impl Event for Click { fn as_any(&self) -> &dyn Any { self }}fn handle(ev: &dyn Event) { if let Some(click) = ev.as_any().downcast_ref::<Click>() { println!("click at {},{}", click.x, click.y); } else { println!("unhandled event"); }}// For enums (the idiomatic alternative to trait-object dispatch),// matching stays exhaustive and needs no downcasting at all.enum AppEvent { Click(Click), Key(char) }fn route(ev: &AppEvent) { match ev { AppEvent::Click(Click { x, y }) => println!("{x},{y}"), AppEvent::Key(c) => println!("key {c}"), }}
Refutability & Advanced Pattern Vocabulary
Terms the compiler uses when reasoning about which patterns are allowed where.
- Irrefutable pattern- Always matches (e.g. a plain variable binding `x`); required in `let`, function params, and `for` loops
- Refutable pattern- May fail to match (e.g. `Some(x)`); allowed in `match` arms, `if let`, `while let`, and `let-else`
- Binding mode- The by-value/by-reference mode match ergonomics infers per-binding when matching against a reference
- Subpattern- A pattern nested inside another, e.g. the `x` inside `Some(x)`
- Non-exhaustive match error (E0004)- Compile error when a match doesn't cover every possible value of the type
- #[non_exhaustive]- Enum/struct attribute forcing downstream crates to always include a `_` arm, preserving future extensibility
- Default binding modes (RFC 2005)- The formal name for the match-ergonomics feature that auto-inserts `ref`/`ref mut`
match is exhaustive by compiler enforcement — if you add a new enum variant later, every match on it fails to compile until you handle the new case, making enums+match a powerful tool for safe refactors.