Rust LogoRefutability: Whether a Pattern Might Fail to Match

In Rust, patterns are used to destructure values, bind variables, and control program flow. The concept of 'refutability' describes whether a given pattern is guaranteed to match any possible value of its type or if it might fail to match certain values.

There are two types of patterns based on refutability:

1. Irrefutable Patterns (Guaranteed to Match):
An irrefutable pattern is one that cannot fail to match a value of the type it's being compared against. These patterns always succeed. They are typically used in contexts where a match is expected to always occur, such as `let` statements.
* Examples of Irrefutable Patterns:
* Variable bindings (e.g., `x`, `_` (wildcard))
* Structs or tuples where all fields are covered by irrefutable patterns (e.g., `Point { x, y }`, `(a, b)`)
* Slices that match the entire slice (e.g., `&[a, b, ..]`)

2. Refutable Patterns (Might Fail to Match):
A refutable pattern is one that can potentially fail to match a given value. These patterns are used when you only care about specific cases or variants of a type. They are commonly used in `if let` expressions and `match` arms, where handling of non-matching cases is explicitly allowed or required.
* Examples of Refutable Patterns:
* Literal values (e.g., `5`, `'A'`, `"hello"`)
* Specific enum variants (e.g., `Some(x)`, `Err(e)`)
* Structs with specific field values (e.g., `Point { x: 0, y }`)
* Range patterns (e.g., `1..=5`)

Contexts and Refutability Rules:

* `let PATTERN = EXPRESSION;`: This construct *requires* an irrefutable pattern. If you use a refutable pattern here, the compiler will raise an error because `let` expects to always successfully bind the variables. If a refutable pattern could fail, it would mean the variables might not be initialized, which Rust's `let` statement does not permit.

* `if let PATTERN = EXPRESSION { ... } else { ... }`: This construct *allows* refutable patterns. It's designed specifically to handle cases where a pattern might or might not match. If the pattern matches, the `if` block executes; otherwise, the `else` block (if present) executes, or nothing happens.

* `while let PATTERN = EXPRESSION { ... }`: Similar to `if let`, this loop continues to execute as long as the refutable pattern matches the expression.

* `match EXPRESSION { PATTERN => { ... }, ... }`: Each arm of a `match` statement uses a refutable pattern. The Rust compiler ensures that `match` statements are *exhaustive*, meaning all possible cases for the `EXPRESSION`'s type are covered by at least one pattern arm. If not all cases are explicitly covered, the compiler requires a wildcard pattern (`_`) as the last arm to catch any remaining values, effectively making the `match` statement exhaustive.

Example Code

use std::fmt::Debug;

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn process_value<T: Debug>(val: T) {
    println!("Processing: {:?}", val);
}

fn main() {
    // --- Irrefutable Patterns ---

    // 1. Variable binding: 'x' is irrefutable, matches any i32.
    let x = 10;
    println!("let x = {}; (irrefutable)", x);

    // 2. Tuple destructuring: '(a, b)' is irrefutable for any 2-tuple.
    let (a, b) = (1, 2);
    println!("let (a, b) = ({}, {}); (irrefutable)", a, b);

    // 3. Struct destructuring with all fields as variable bindings: 'p' is irrefutable for any Point.
    struct Point { x: i32, y: i32 }
    let p = Point { x: 5, y: 7 };
    let Point { x: px, y: py } = p;
    println!("let Point {{ x: px, y: py }} = {{ x:{}, y:{} }}; (irrefutable) -> px={}, py={}", p.x, p.y, px, py);

    // --- Refutable Patterns ---

    // Attempting to use a refutable pattern with `let` will result in a compile error.
    // Uncomment the line below to see the error: "refutable pattern in local binding"
    // let Some(value) = Some(10); // ERROR!

    let some_num = Some(20);
    let none_num: Option<i32> = None;

    // 1. 'if let' for refutable patterns (enum variants)
    if let Some(val) = some_num {
        println!("if let Some({}) = some_num; (refutable) -> Matched Some", val);
    } else {
        println!("if let Some({}) = some_num; (refutable) -> Did not match Some", 0); // Placeholder for None case
    }

    if let Some(val) = none_num {
        println!("if let Some({}) = none_num; (refutable) -> Matched Some", val);
    } else {
        println!("if let Some({}) = none_num; (refutable) -> Did not match Some", 0); // Placeholder for None case
    }

    // 2. 'match' statement for refutable patterns (enum variants, literals, ranges)
    let msg = Message::Move { x: 10, y: 20 };

    match msg {
        Message::Quit => {
            println!("Match: Quit message received.");
        }
        Message::Move { x, y } => {
            println!("Match: Move to x:{}, y:{}.", x, y);
            process_value(x); // Example usage of bound variables
        }
        Message::Write(text) => {
            println!("Match: Writing text: {}.", text);
        }
        // Refutable pattern: specific field values & ranges combined
        Message::ChangeColor(0..=255, 0, b) if b > 100 => {
             println!("Match: Change color to R, G=0, B > 100.");
        }
        // Irrefutable pattern as a catch-all for remaining cases
        _ => {
            println!("Match: Some other message.");
        }
    }

    let status_code = 200;
    match status_code {
        // Refutable pattern: literal
        200 => println!("Match: OK"),
        // Refutable pattern: range
        400..=499 => println!("Match: Client error"),
        // Refutable pattern: single wildcard (irrefutable for the rest)
        _ => println!("Match: Unknown status"),
    }

    // 3. 'while let' for refutable patterns
    let mut optional_value = Some(3);
    let mut counter = 0;
    while let Some(value) = optional_value {
        println!("While let: Current value: {}", value);
        counter += 1;
        if counter == 2 {
            optional_value = None; // Break the loop
        }
    }
    println!("While let: Loop finished. Counter: {}", counter);
}