Enums and Pattern Matching

Wildcard Patterns

Rust provides three ways to match values without naming every component: the single-value wildcard (_), which matches any value without binding or moving it; a catch-all variable binding (other or _unused), which matches any value and binds it; and the rest pattern (..), which ignores multiple fields or elements.

Crucial Difference Between _ and _name

Unlike a variable prefixed with an underscore (_x), the bare underscore pattern _ never binds, moves, or borrows the matched value:

Pattern Binds the Value? Moves Non-Copy Data? When Dropped?
_ (Wildcard) No No (the matched value stays untouched in place!) At the end of the enclosing scope as normal
_x (Unused variable) Yes (suppresses unused-variable warning) Yes (moves ownership into _x) At the end of _x's scope
let _ = expr; No Evaluates expr and immediately drops the temporary result at the semicolon Immediately at ;
.. (Rest pattern) No No (ignores remaining struct fields, tuple items, or slice elements) Unchanged
fn main() {
    let maybe_text = Some(String::from("Rust 2024"));

    // Because `Some(_)` uses `_`, the inner `String` is NOT moved out of `maybe_text`!
    if let Some(_) = maybe_text {
        println!("maybe_text contains a value.");
    }

    // `maybe_text` is still completely valid and owns the `String`:
    println!("Still owned: {maybe_text:?}");
}
maybe_text contains a value.
Still owned: Some("Rust 2024")

By contrast, writing if let Some(_text) = maybe_text moves the String into _text, consuming maybe_text.

Using _ and Catch-All Variable Bindings in match

When the final arm of a match needs to inspect or log the unmatched value, use a descriptive variable name (such as other); when the unmatched value is not needed, use _:

fn classify_port(port: u16) -> String {
    match port {
        22 => String::from("SSH"),
        80 | 8080 => String::from("HTTP"),
        443 => String::from("HTTPS"),
        other => format!("Custom port ({other})"),
    }
}

fn main() {
    println!("{}", classify_port(443));
    println!("{}", classify_port(5432));
}
HTTPS
Custom port (5432)

Ignoring Specific Positions vs. Ignoring All Remaining Fields (..)

You can place _ at specific positions inside a tuple or enum variant ((x, _, z)), or use .. to ignore all remaining fields of a struct or elements of a tuple/slice:

struct Telemetry {
    timestamp: u64,
    cpu_load: f32,
    mem_used_mb: u32,
    disk_io_mb: u32,
}

fn main() {
    let reading = Telemetry {
        timestamp: 1700000000,
        cpu_load: 0.75,
        mem_used_mb: 4096,
        disk_io_mb: 12,
    };

    // Ignore all fields except `cpu_load` using `..`:
    let Telemetry { cpu_load, .. } = reading;
    println!("CPU Load: {:.0}%", cpu_load * 100.0);

    // Ignore middle tuple elements using `..` or specific positions using `_`:
    let coords = (10, 20, 30, 40);
    let (first, .., last) = coords;
    let (_, second, _, _) = coords;
    println!("first = {first}, second = {second}, last = {last}");
}
CPU Load: 75%
first = 10, second = 20, last = 40

Unreachable Arms After a Wildcard (unreachable_patterns Warning)

Because match evaluates arms top-to-bottom and _ matches every possible value, any arm placed after a bare _ (without a match guard) can never be reached and triggers a compiler warning:

fn describe(n: u8) -> &'static str {
    match n {
        0 => "zero",
        _ => "nonzero", // Always place the catch-all arm LAST!
    }
}

fn main() {
    println!("{}", describe(5));
}
nonzero