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&p
expects a reference and matchesp
against the value pointed to.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 (especially for non-Copy
types).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 { // 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.