Enums and Pattern Matching

Binding Values in Patterns

The @ (at) binding operator (variable @ subpattern) lets a pattern test a value against a subpattern (such as a range, literal alternative, or nested destructuring pattern) while simultaneously binding the matched value to variable.

Why @ Bindings Are Needed

When you match against a range (100..=199) or an or-pattern (200 | 201 | 204) without @, the pattern verifies that the value falls in the range, but you have no variable name to print or pass the exact matched value inside the arm body:

Pattern Form Tests Against Subpattern? Binds Matched Value to Variable? Destructures Inner Fields?
100..=199 Yes No N/A
code Matches anything Yes (code) No
code @ 100..=199 Yes Yes (code) N/A
pkt @ Packet { id: 1..=10, .. } Yes (id in 1..=10) Yes (pkt = whole Packet) Also allows binding inner fields!

Binding Range and Or-Pattern Matches with @

enum HttpStatus {
    Code(u16),
    Disconnected,
}

fn log_status(status: HttpStatus) {
    match status {
        HttpStatus::Code(code @ (200 | 201 | 204)) => {
            println!("Success status {code}");
        }
        HttpStatus::Code(code @ 400..=499) => {
            println!("Client error {code}: check request payload");
        }
        HttpStatus::Code(code @ 500..=599) => {
            println!("Server error {code}: retry with backoff");
        }
        HttpStatus::Code(other) => {
            println!("Other status {other}");
        }
        HttpStatus::Disconnected => {
            println!("Connection closed before status received");
        }
    }
}

fn main() {
    log_status(HttpStatus::Code(204));
    log_status(HttpStatus::Code(404));
    log_status(HttpStatus::Code(502));
}
Success status 204
Client error 404: check request payload
Server error 502: retry with backoff

Binding Both the Whole Struct and Its Destructured Fields

For Copy types (or when matching by reference so no conflicting move + borrow occurs), you can bind the outer value with whole @ ... while also binding or testing inner fields inside the subpattern:

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn inspect_point(pt: Point) {
    match pt {
        // Bind the entire `Point` to `origin_axis` AND extract `x` and `y`:
        origin_axis @ Point { x: 0, y } | origin_axis @ Point { x: y, y: 0 } => {
            println!("On axis: {origin_axis:?} (nonzero coordinate = {y})");
        }
        whole @ Point {
            x: 1..=10,
            y: 1..=10,
        } => {
            println!("Inside 10x10 quadrant: {whole:?}");
        }
        other => println!("Outside target region: {other:?}"),
    }
}

fn main() {
    inspect_point(Point { x: 0, y: 7 });
    inspect_point(Point { x: 4, y: 8 });
    inspect_point(Point { x: -3, y: 12 });
}
On axis: Point { x: 0, y: 7 } (nonzero coordinate = 7)
Inside 10x10 quadrant: Point { x: 4, y: 8 }
Outside target region: Point { x: -3, y: 12 }

Using @ .. in Slice Patterns

Inside a slice pattern ([first, middle @ .., last]), attaching name @ to the rest pattern .. binds the matched middle subslice (&[T] or &mut [T]) to name:

fn main() {
    let bytes: &[u8] = &[0xAA, 10, 20, 30, 0xFF];

    if let [header, body @ .., checksum] = bytes {
        println!("header: 0x{header:02X}, body: {body:?}, checksum: 0x{checksum:02X}");
    }
}
header: 0xAA, body: [10, 20, 30], checksum: 0xFF