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.ref
Keyword: 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.ref mut
Keyword: 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 { &10 => println!("Value is 10 (matched via &)"), // `&10` matches `&i32` _ => {} } // Example with Option<&T> let opt_ref: Option<&String> = Some(&"hello".to_string()); match opt_ref { Some(&ref s) => println!("Got reference to string: {}", s), // `&ref s` matches `&String`, `s` is &String 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 { // `ref s` makes `s` an `&String`, borrowing from `maybe_owned_string` Some(ref s) => { println!("Borrowed string: {}", s); // `maybe_owned_string` is still owned outside the match, because `s` only borrows } None => {} } // We can still use maybe_owned_string here if it wasn't None if let Some(s) = maybe_owned_string { println!("Original Option still contains: {}", s); } // 3. Using `ref mut` to modify through a mutable reference let mut maybe_count: Option<u32> = Some(5); match maybe_count { // `ref mut c` makes `c` an `&mut u32`, mutably borrowing 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
) if you don’t want the pattern matching to take ownership of those parts.