๐Ÿฆ€ Functional Rust
๐ŸŽฌ Pattern Matching Exhaustive match, destructuring, guards, let-else โ€” compiler-verified branching.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข match is exhaustive โ€” the compiler ensures every variant is handled

โ€ข Destructuring extracts fields from structs, tuples, and enums in one step

โ€ข Guards (if conditions) add extra logic to match arms

โ€ข if let and let-else provide concise single-pattern matching

โ€ข Nested patterns and @ bindings handle complex data shapes elegantly

577: Irrefutable vs Refutable Patterns

Difficulty: 2 Level: Beginner Some patterns always match; others might not โ€” and Rust's syntax reflects the difference.

The Problem This Solves

Rust distinguishes between patterns that always succeed (irrefutable) and patterns that might fail (refutable). This distinction determines where each kind of pattern is allowed. `let` bindings require irrefutable patterns. `if let`, `while let`, and `match` arms accept refutable ones. Mixing them up is a compile error with a helpful message, but understanding the distinction from the start prevents the confusion entirely. Irrefutable patterns are everywhere: every `let` statement, every function parameter, every `for` loop. They're so common you may not think of them as "patterns" at all. Refutable patterns โ€” `Some(v)`, `Ok(x)`, variant names โ€” can fail to match, so they need a context that handles the failure.

The Intuition

An irrefutable pattern is a guarantee: no matter what value you give it, it will match. `let x = expr` always works โ€” `x` can hold any value. `let (a, b, c) = (1, 2, 3)` always works โ€” a 3-tuple always has three elements. A struct destructure in a `let` always works โ€” the struct always has those fields. A refutable pattern might say "no": `Some(v)` doesn't match `None`. That's why you need `if let` โ€” it provides an else branch for when the pattern doesn't match. Using a refutable pattern in a plain `let` is a compile error because there's no way to handle the "doesn't match" case.

How It Works in Rust

1. Irrefutable `let` โ€” `let x = 42; let (a, b) = (1, 2); let Point { x, y } = p;` โ€” always match, no `if` needed. 2. Irrefutable function params โ€” `fn add((a, b): (i32, i32)) -> i32 { a + b }` โ€” tuple destructuring in the parameter position. 3. Irrefutable `for` โ€” `for (n, ch) in &pairs` โ€” destructures each element; works because every pair is a 2-tuple. 4. Refutable `if let` โ€” `if let Some(v) = opt { ... }` โ€” handles the case where it doesn't match. 5. Refutable `while let` โ€” `while let Some(t) = stack.pop() { ... }` โ€” loops until the pattern fails to match.

What This Unlocks

Key Differences

ConceptOCamlRust
Irrefutable let`let (a, b) = pair` โ€” always worksSame: `let (a, b) = pair`
Refutable patterns`match opt with Some v -> ... \None -> ...``if let Some(v) = opt { ... }` or `match`
For loop destructuring`List.iter (fun (n, ch) -> ...)``for (n, ch) in &pairs` โ€” irrefutable destructuring
Refutable in letCompiler warns of non-exhaustive; may generate exceptionCompile error: "refutable pattern in local binding"
// โ”€โ”€ Irrefutable patterns โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn irrefutable() {
    let x = 42;                          // simple variable
    let (a, b, c) = (1, 2, 3);          // tuple
    struct P { x: f64, y: f64 }
    let P { x, y } = P { x: 1.0, y: 2.0 }; // struct
    fn add((a,b): (i32,i32)) -> i32 { a+b }  // irrefutable param
    let pairs = vec![(1,'a'),(2,'b')];
    for (n, ch) in &pairs { println!("{}{}", n, ch); } // irrefutable for
    println!("{} {} {} {} {} {}", x, a, b, c, add((3,4)), y+x);
}

// โ”€โ”€ Refutable patterns โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
fn refutable() {
    let opt: Option<i32> = Some(42);

    // if let โ€” OK, refutable
    if let Some(v) = opt { println!("Some({})", v); }

    // match โ€” OK, refutable
    match opt { Some(v) => println!("match {}", v), None => {} }

    // while let โ€” OK, refutable
    let mut s = vec![1,2,3];
    while let Some(t) = s.pop() { print!("{} ", t); } println!();

    // This FAILS to compile (uncomment to see error):
    // let Some(v) = opt;   // ERROR: refutable pattern in local binding
}

fn main() { irrefutable(); refutable(); }

#[cfg(test)]
mod tests {
    #[test] fn irr_tuple() { let (a,b) = (10,20); assert_eq!(a+b, 30); }
    #[test] fn irr_struct() {
        struct F { x: i32 }
        let F { x } = F { x: 42 }; assert_eq!(x, 42);
    }
}
(* Irrefutable vs refutable in OCaml *)
let () =
  (* Irrefutable let bindings *)
  let (a, b) = (1, 2) in
  Printf.printf "a=%d b=%d\n" a b;

  (* Irrefutable function param *)
  let add (x,y) = x+y in
  Printf.printf "add=%d\n" (add (3,4));

  (* Refutable โ€” requires match *)
  let opt = Some 42 in
  (match opt with Some v->Printf.printf "got %d\n" v | None->());

  (* OCaml warns if you do: let Some v = opt *)
  (* That pattern is "non-exhaustive" โ€” use match instead *)
  ()