21.12 Destructuring Arrays, Slices, Tuples, Structs, Enums, and References
21.12.1 Arrays and Slices
fn inspect_array(arr: &[i32]) { match arr { [] => println!("Empty slice"), [first, .., last] => println!("First: {}, Last: {}", first, last), [_] => println!("One item only"), } } fn main() { let data = [1, 2, 3, 4, 5]; inspect_array(&data); }
The following example showcases more intricate uses of slice patterns. It demonstrates many features at once, including binding specific elements, ignoring some elements, and capturing the “middle” portion via @ ..
.
fn main() { let array = [1, -2, 6]; // known size, so the code below covers all cases! // let array = &array[0..3]; // for a slice we would have to test for `&[]` and `&[_]` as well! match array { // Binds the second and third elements to separate variables [0, second, third] => println!( "array[0] = 0, array[1] = {}, array[2] = {}", second, third ), // a single elements can be ignored with `_` [1, _, third] => println!( "array[0] = 1, array[2] = {} and array[1] was ignored", third ), // You can also bind one or two elements and ignore the rest [-1, second, ..] => println!( "array[0] = -1, array[1] = {} and all other elements were ignored", second ), // Store the remaining elements in a separate slice or array (type depends on the match input) [3, second, tail @ ..] => println!( "array[0] = 3, array[1] = {} and the other elements were {:?}", second, tail ), // Combine these patterns by binding the first and last elements // and storing the middle ones in a slice [first, middle @ .., last] => println!( "array[0] = {}, middle = {:?}, array[2] = {}", first, middle, last ), } }
21.12.2 Key Observations
- Ignoring Elements: Use
_
or..
to skip over parts of the array. - Binding Remainder:
tail @ ..
ormiddle @ ..
captures any leftover elements, letting you treat them as a separate slice (the type depends on whether you’re matching against an array or a slice). - Combining Patterns: You can mix these features to match very specific shapes (
[3, second, tail @ ..]
) or more general ones ([first, .., last]
).
This snippet illustrates just how powerful Rust’s destructuring can be for arrays (or slices). By matching partial or complete patterns, you can elegantly handle all kinds of scenarios without manually writing index operations.
21.12.3 Tuples
fn sum_tuple(pair: (i32, i32)) -> i32 { let (a, b) = pair; a + b } fn main() { println!("{}", sum_tuple((10, 20))); }
21.12.4 Structs
struct User { name: String, active: bool, } fn print_user(user: User) { match user { User { name, active: true } => println!("{} is active", name), User { name, active: false } => println!("{} is inactive", name), } } fn main() { let alice = User { name: String::from("Alice"), active: true }; print_user(alice); }
21.12.5 Enums
Enums can be nested, and you can destructure them deeply:
enum 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, } } fn main() { let c = Shape::Circle { radius: 3.0 }; println!("Circle area: {}", area(c)); }
21.12.6 Pattern Matching With References
Rust provides multiple ways to match against references, whether those references are in an Option
or directly in a variable. Consider the following examples:
fn main() { // Example 1: Option of a reference let value = Some(&42); match value { // Matches "Some(&val)" when we stored a reference: Some(&42) Some(&val) => println!("Got a value by dereferencing: {}", val), None => println!("No value found"), } // Example 2: Matching a reference directly using "*reference" let reference = &10; match *reference { 10 => println!("The reference points to 10"), _ => println!("The reference points to something else"), } // Example 3: "ref r" let some_value = Some(5); match some_value { Some(ref r) => println!("Got a reference to the value: {}", r), None => println!("No value found"), } // Example 4: "ref mut m" let mut mutable_value = Some(8); match mutable_value { Some(ref mut m) => { *m += 1; // Modify through the mutable reference println!("Modified value through mutable reference: {}", m); } None => println!("No value found"), } }
Key Points:
- Direct Matching:
Some(@val)
matches an integer reference stored inSome(...)
. - Dereferencing:
match *reference
manually dereferences a reference. ref
andref mut
: Borrow a reference (immutable or mutable) to the inner value, preventing a move. This can be especially helpful when you want to avoid transferring ownership or when you need to modify data in place.