Enums and Pattern Matching

Refutable vs. Irrefutable Patterns

Every pattern in Rust is classified as either irrefutable (guaranteed at compile time to match every possible value of its input type) or refutable (capable of failing to match for some possible runtime values).

Where Each Pattern Kind Is Accepted

Language Construct Accepts Irrefutable Patterns? Accepts Refutable Patterns? Reason
let pat = expr; Yes No (E0005) Has no fallback branch if the pattern fails
fn f(pat: Type) / |pat| Yes No (E0005) Function call has no fallback branch if argument fails to match
for pat in iter Yes No (E0005) Loop runs for every yielded item
let pat = expr else { ... }; Warns (irrefutable_let_patterns) Yes else block handles mismatch
if let pat = expr Warns (irrefutable_let_patterns) Yes Condition is meant to branch on potential mismatch
while let pat = expr Warns (irrefutable_let_patterns) Yes Loop terminates when pattern stops matching
match expr { pat => ... } Yes (as the final catch-all arm) Yes (as individual arms) Arms test refutable cases until all cases are covered

Examples of Irrefutable vs. Refutable Patterns

Pattern Scrutinee Type Classification Why
x, _, (a, b) (i32, i32) Irrefutable Every (i32, i32) is a 2-tuple
Point { x, y } struct Point { x: i32, y: i32 } Irrefutable A struct has only one shape
[a, b, c] [i32; 3] (fixed array) Irrefutable Array length 3 is guaranteed at compile time
[a, b, c] &[i32] (slice) Refutable Slice length at runtime might not be 3
Some(x) Option<i32> Refutable Value might be None at runtime
Ok(v) Result<T, !> (or uninhabited Err) Irrefutable Because ! has zero values, Err can never exist

Error: Using a Refutable Pattern in let (E0005)

Attempting to unpack Some(x) in a plain let statement fails because let has no way to handle None:

// ❌ does not compile
fn main() {
    let maybe_port: Option<u16> = Some(8080);
    let Some(port) = maybe_port;
    println!("{port}");
}
error[E0005]: refutable pattern in local binding
 --> src/main.rs:3:9
  |
3 |     let Some(port) = maybe_port;
  |         ^^^^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
help: you might want to use `let else` to handle the variant that isn't matched
  |
3 |     let Some(port) = maybe_port else { todo!() };
  |                                 ++++++++++++++++

Fix: Use if let or let-else for Refutable Patterns

fn main() {
    let maybe_port: Option<u16> = Some(8080);

    // Fix 1: `let-else` when you want `port` bound in the outer scope:
    let Some(port) = maybe_port else {
        return;
    };
    println!("Listening on port {port}");

    // Fix 2: Irrefutable destructuring of a single-variant newtype or tuple in `let`:
    struct Meters(f64);
    let Meters(distance) = Meters(42.5);
    println!("Distance: {distance} m");
}
Listening on port 8080
Distance: 42.5 m