21.11 Destructuring Data Structures
A major strength of patterns is destructuring: breaking down composite types into their constituent parts.
21.11.1 Tuples
fn process_3d_point(point: (i32, i32, i32)) {
match point {
(0, 0, 0) => println!("At the origin"),
(x, 0, 0) => println!("On X-axis at {}", x),
(0, y, 0) => println!("On Y-axis at {}", y),
(0, 0, z) => println!("On Z-axis at {}", z),
(x, y, z) => println!("General point at ({}, {}, {})", x, y, z),
}
}
fn main() {
process_3d_point((5, 0, 0)); // Output: On X-axis at 5
process_3d_point((0, -2, 0)); // Output: On Y-axis at -2
process_3d_point((1, 2, 3)); // Output: General point at (1, 2, 3)
}
21.11.2 Structs
Use field names to destructure. Field name punning ({ field } for { field: field }) is common.
struct User {
id: u64,
name: String,
is_admin: bool,
}
fn describe_user(user: &User) {
match user {
// Use punning for name, specify is_admin, ignore id with `..`
User { name, is_admin: true, .. } => {
println!("Admin user: {}", name);
}
// Use specific id, pun name, specify is_admin
User { id: 0, name, is_admin: false } => {
println!("Special guest user (ID 0): {}", name);
}
// Use punning for name, ignore other fields
User { name, .. } => {
println!("Regular user: {}", name);
}
}
}
fn main() {
let admin = User { id: 1, name: "Alice".to_string(), is_admin: true };
let guest = User { id: 0, name: "Guest".to_string(), is_admin: false };
let regular = User { id: 2, name: "Bob".to_string(), is_admin: false };
describe_user(&admin); // Output: Admin user: Alice
describe_user(&guest); // Output: Special guest user (ID 0): Guest
describe_user(®ular); // Output: Regular user: Bob
}
21.11.3 Arrays and Slices
Match fixed-size arrays or variable-length slices by elements.
fn analyze_slice(data: &[u8]) {
match data {
[] => println!("Empty slice"),
[0] => println!("Slice contains only 0"),
[1, x, y] => println!("Slice starts with 1, followed by {}, {}", x, y),
// Match first element, ignore middle (`..`), bind last
[first, .., last] => {
println!("Slice starts with {} and ends with {}", first, last);
}
// Match fixed prefix [0, 1], capture the rest in `tail`
[0, 1, tail @ ..] => {
println!("Slice starts [0, 1], rest is {:?}", tail);
}
// Fallback using wildcard `_`
_ => println!("Slice has {} elements, didn't match specific patterns",
data.len()),
}
}
fn main() {
analyze_slice(&[]); // Output: Empty slice
analyze_slice(&[0]); // Output: Slice contains only 0
analyze_slice(&[1, 5, 8]); // Output: Slice starts with 1, followed by 5, 8
analyze_slice(&[10, 20, 30, 40]); // Output: Slice starts with 10 and ends with 40
analyze_slice(&[0, 1, 2, 3]); // Output: Slice starts [0, 1], rest is [2, 3]
analyze_slice(&[2, 3]); // Output: Slice has 2 elements...
}
Key slice/array patterns:
[a, b, c]: Matches exactly 3 elements.[head, ..]: Matches 1 or more elements, bindshead.[.., tail]: Matches 1 or more elements, bindstail.[first, .., last]: Matches 2 or more elements.[prefix.., name @ .., suffix..]: Captures sub-slices.
21.11.4 Matching References and Using ref/ref mut
When matching references or needing to borrow within a pattern (to avoid moving values), use &, ref, and ref mut.
&in Pattern: Matches a value held within a reference. The pattern&pexpects a reference and matchespagainst the value pointed to.refKeyword: Creates an immutable reference (&T) to a field or element within the matched value. Use this when matching by value but need to borrow parts instead of moving them (especially for non-Copytypes).ref mutKeyword: Creates a mutable reference (&mut T). Use this when matching by value or mutable reference and need mutable access to parts without moving.
fn main() {
// 1. Matching `&` directly
let reference_to_val: &i32 = &10;
match reference_to_val {
// Here, `&10` means: expect `reference_to_val` to be a reference,
// and check if the value it points to is 10.
&10 => println!("Value is 10 (matched via &)"),
_ => {}
}
// Example with Option<&T>
let owned_string = "hello".to_string(); // Create an owned String that lives longer
let opt_ref: Option<&String> = Some(&owned_string);
// opt_ref now holds a valid reference
match opt_ref {
// `opt_ref` contains an `&String`.
// The pattern `Some(&ref s)` means:
// - The `Some` variant is expected.
// - The `&` in `&ref s` matches the `&String` from `opt_ref`.
// It effectively says:
// "The value inside Some is a reference; match against what it
// *points* to (the String)."
// - `ref s` then matches against that `String`. Instead of trying
// to move the `String` (which isn't Copy),
// `ref s` creates a new reference `s` (of type `&String`) to that `String`.
Some(&ref s) => println!("Got reference to string: {}", s),
None => {}
}
// 2. Using `ref` to borrow from an owned value being matched
let maybe_owned_string: Option<String> = Some("world".to_string());
match maybe_owned_string {
// `maybe_owned_string` contains a `String`.
// `Some(s)` would try to move the String out of the Option.
// `Some(ref s)` makes `s` an `&String`, borrowing from `maybe_owned_string`.
Some(ref s) => {
println!("Borrowed string: {}", s);
// `maybe_owned_string` is still owned and valid here
// because `s` only borrows.
}
None => {}
}
// We can still use maybe_owned_string here if it wasn't None
if let Some(s_val) = &maybe_owned_string { // Borrow for inspection
println!("Original Option still contains: {}", s_val);
}
// 3. Using `ref mut` to modify through a mutable reference
let mut maybe_count: Option<u32> = Some(5);
match maybe_count {
// `maybe_count` contains a `u32`.
// `Some(ref mut c)` makes `c` an `&mut u32`, mutably borrowing
// from `maybe_count`.
Some(ref mut c) => {
*c += 1;
println!("Incremented count: {}", c);
}
None => {}
}
println!("Final count: {:?}", maybe_count); // Output: Final count: Some(6)
}
Using ref and ref mut is essential when destructuring non-Copy types (like String, Vec) from within an owned context (like Option<String>) if you don’t want the pattern matching to take ownership of those parts. When matching against existing references (like &String or &mut T), & in the pattern allows you to “see through” the reference, and ref or ref mut may then be needed to re-borrow the underlying data.