Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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(&regular); // 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, binds head.
  • [.., tail]: Matches 1 or more elements, binds tail.
  • [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.

  1. & in Pattern: Matches a value held within a reference. The pattern &p expects a reference and matches p against the value pointed to.
  2. 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).
  3. 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.