Patterns are special syntax in Rust for matching against the structure of values. Matching is the act of comparing a value against a pattern. This powerful feature allows for concise and safe handling of complex data, controlling program flow, and destructuring data structures.
Where Patterns Are Used
Patterns can be used in several places in Rust code:
1. `match` Expressions: The most common and powerful way to use patterns. A `match` expression takes a value and compares it against a series of patterns, executing the code block of the first matching pattern. It must be exhaustive, meaning all possible values must be covered by at least one pattern.
2. `if let` Expressions: A concise way to handle a single pattern. It's syntactic sugar for a `match` that only cares about one successful pattern match and ignores all other cases, optionally providing an `else` block.
3. `while let` Expressions: Similar to `if let`, but it continues to loop as long as the pattern matches, processing elements until the pattern no longer applies (e.g., iterating through an `Option<T>` until it becomes `None`).
4. `for` Loops: Patterns can destructure elements yielded by an iterator. For example, `for (key, value) in hashmap` destructures tuples.
5. `let` Statements: Used for simple variable assignment and destructuring of tuples, structs, and enums (e.g., `let (x, y) = point;`). These patterns must be *irrefutable*.
6. Function Parameters: Patterns can be used in function signatures to destructure input arguments directly (e.g., `fn foo((x, y): (i32, i32))`). These patterns must also be *irrefutable*.
Types of Patterns
Rust supports a variety of patterns, each serving a specific purpose:
* Literals: Match against exact values (e.g., `5`, `"hello"`, `true`, `3.14`).
* Variables: Bind the matched value to a new variable (e.g., `x`, `user_id`). These are *irrefutable* in `let` statements and function parameters.
* Wildcard Pattern (`_`): Ignores a part of the value. It never binds to anything and is useful for matching remaining cases or parts of a value you don't care about.
* Placeholder for Remaining Values (`..`): Used in struct, tuple, or array patterns to ignore some parts of the value. For example, `Point { x, .. }` matches a `Point` struct and binds its `x` field, ignoring all other fields.
* Struct Patterns: Match and destructure fields of a struct (e.g., `Point { x, y }` or `Config { debug: true, .. }`). You can optionally rename fields (e.g., `Point { x: my_x, y: my_y }`).
* Enum Patterns: Match against specific variants of an enum, optionally destructuring their associated data (e.g., `Option::Some(value)`, `Result::Ok(data)`).
* Tuple Patterns: Match and destructure elements of a tuple (e.g., `(x, y)`, `(a, _, c)`).
* Slice/Array Patterns: Match against elements of an array or slice. You can specify a fixed number of elements (`[first, second]`) or use `..` to match any remaining elements (`[head, .., tail]`).
* Reference Patterns (`&`): Match against references (e.g., `&val`, `&mut val`). This borrows the matched value rather than moving or copying it.
* Range Patterns: Match against a range of values (e.g., `1..=5`, `'a'..='z'`). The range is inclusive on both ends.
* `ref` and `ref mut`: These keywords allow you to borrow a value within a pattern rather than moving it or copying it. `ref x` creates an immutable reference to the matched value, while `ref mut x` creates a mutable one. This is particularly useful when matching on owned data that you don't want to move out of the parent structure.
Key Concepts
* Destructuring: Patterns are primarily used to break down complex data structures (like tuples, structs, and enums) into their constituent parts, binding those parts to new variables.
* Refutability: Patterns are categorized as either *refutable* or *irrefutable*.
* Irrefutable Patterns: Patterns that are guaranteed to match for any possible value. Examples include `x` (a variable binding), `_` (the wildcard), `(x, y)`, or `Point { x, y }` if `Point` is the only variant of an enum or if you're destructuring a concrete `Point` instance. These are used in contexts where a match is always expected, like `let` statements, `for` loops, and function parameters.
* Refutable Patterns: Patterns that might fail to match for some values. Examples include `Some(x)` (which doesn't match `None`), `Ok(val)` (which doesn't match `Err`), or `Point { x, y }` when matching an enum that might have other variants. These are used in `match` expressions (where all possible cases must be covered exhaustively) and `if let`/`while let` (where a failure to match is handled gracefully or ignored).
* Match Guards: An `if` condition can be added to a `match` arm after the pattern (e.g., `Some(x) if x > 0 => ...`). This condition provides an additional filter that must be true for the arm to match. If the pattern matches but the guard is false, the `match` expression proceeds to the next arm.
* `@` Bindings (At Operator): The `at` operator (`@`) allows you to bind a value to a variable *while also* matching against it with another pattern. For example, `value @ 1..=10` will bind the matched value to `value` and ensure it's within the range `1` to `10`. This is useful when you want to use the full value that matched a sub-pattern.
Example Code
```rust
// --- 1. Basic `match` Expression ---
fn describe_number(x: i32) {
match x {
1 => println!("One"),
2 | 3 => println!("Two or Three"), // Multiple patterns
4..=6 => println!("Four to Six"), // Range pattern (inclusive)
_ => println!("Something else"), // Wildcard pattern
}
}
// --- 2. `match` with Enums and Destructuring ---
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!("The Quit message has no data.");
}
Message::Move { x, y } => { // Destructuring struct fields
println!("Move to x: {}, y: {}", x, y);
}
Message::Write(text) => { // Destructuring tuple struct content
println!("Text message: '{}'", text);
}
Message::ChangeColor(r, g, b) => { // Destructuring tuple content
println!("Change color to R:{}, G:{}, B:{}", r, g, b);
}
}
}
// --- 3. `if let` and `while let` ---
fn handle_option_value() {
let some_value = Some(7);
let none_value: Option<i32> = None;
// `if let` to handle Some variant
if let Some(value) = some_value {
println!("Got a value with if let: {}", value);
}
// `if let` with `else`
if let Some(_) = none_value {
// This block will not execute
println!("This will not print.");
} else {
println!("No value found for none_value with if let.");
}
// `while let` to pop elements from a vector (acts like a stack)
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("Popped {} from stack", top);
}
println!("Stack is empty.");
}
// --- 4. `let` Statement Destructuring ---
struct Point {
x: i32,
y: i32,
}
fn destructure_with_let() {
// Tuple destructuring
let (a, b, c) = (10, 'H', true);
println!("Tuple destructuring: a={}, b={}, c={}", a, b, c);
// Struct destructuring (with renaming and `..` for ignored fields)
let p = Point { x: 100, y: 200 };
let Point { x: p_x, y: p_y } = p; // Destructure and rename fields
println!("Struct destructuring (renamed): p_x={}, p_y={}", p_x, p_y);
let Point { x, .. } = p; // Destructure `x`, ignore `y` with `..`
println!("Struct destructuring (partial with '..'): x={}", x);
// Enum destructuring within a `let` (must be irrefutable)
enum OnlyOne {
Variant(i32),
}
let OnlyOne::Variant(val) = OnlyOne::Variant(42);
println!("Enum destructuring (irrefutable): {}", val);
}
// --- 5. Function Parameters as Patterns ---
// Destructuring a tuple directly in the function signature
fn print_coordinates((x, y): (i32, i32)) {
println!("Coordinates: ({}, {})", x, y);
}
// --- 6. `for` Loop Destructuring ---
fn iterate_with_patterns() {
let v = vec![Some(1), None, Some(3)];
// Using `if let` inside a `for` loop
for i in v {
if let Some(val) = i {
println!("Found some value in for loop: {}", val);
}
}
// Destructuring tuples directly in a `for` loop
let points = vec![(1, 2), (3, 4), (5, 6)];
for (x, y) in points {
println!("Point from for loop: ({}, {})", x, y);
}
// Destructuring structs within a `for` loop (requires `ref` if not moved)
struct Item { id: u32, name: String }
let items = vec![Item { id: 1, name: "Apple".to_string() }, Item { id: 2, name: "Banana".to_string() }];
for Item { id, name } in items {
println!("Item: id={}, name={}", id, name);
}
}
// --- 7. `match` Guards and `@` Bindings ---
fn advanced_matching(val: Option<i32>) {
match val {
Some(x) if x < 0 => println!("Negative Some: {}", x), // Match guard
Some(x) if x == 0 => println!("Zero Some: {}", x), // Match guard
Some(x @ 1..=10) => println!("Some value 1-10: {}", x), // `@` binding + range
Some(x) => println!("Positive Some (other): {}", x),
None => println!("No value"),
}
}
fn main() {
println!("--- Basic `match` ---");
describe_number(1);
describe_number(5);
describe_number(10);
println!("\n--- `match` with Enums ---");
process_message(Message::Quit);
process_message(Message::Move { x: 10, y: 20 });
process_message(Message::Write(String::from("hello world")));
process_message(Message::ChangeColor(255, 0, 128));
println!("\n--- `if let` and `while let` ---");
handle_option_value();
println!("\n--- `let` Destructuring ---");
destructure_with_let();
println!("\n--- Function Parameter Patterns ---");
print_coordinates((50, 100));
println!("\n--- `for` Loop Patterns ---");
iterate_with_patterns();
println!("\n--- Advanced Matching (Guards & @) ---");
advanced_matching(Some(-5));
advanced_matching(Some(0));
advanced_matching(Some(5));
advanced_matching(Some(15));
advanced_matching(None);
}
```








Patterns and Matching