Refutability: Whether a Pattern Might Fail to Match

Patterns come in two forms: refutable and irrefutable. It's important to understand the difference because the Oxide compiler enforces certain rules about which patterns can be used where.

Irrefutable Patterns

An irrefutable pattern is a pattern that will always match for any value you pass to it. Examples include:

let x = 5
let (x, y) = (1, 2)
let Point { x, y } = point

These patterns always match because they bind to values unconditionally. Irrefutable patterns are the ones you'll most commonly use in let statements, function parameters, and other places where the pattern must always match.

Refutable Patterns

A refutable pattern is one that might not match for some values. Examples include:

Some(x) // might be null instead
Coin.Penny // might be a different coin variant
n if n > 5 // might not satisfy the condition

These patterns could fail to match some values. When you use a refutable pattern, you need to handle the case where the pattern doesn't match.

Where Each Pattern Type Is Allowed

The Oxide compiler requires irrefutable patterns in certain contexts and allows refutable patterns in others:

Irrefutable Patterns Required

In these contexts, you must use irrefutable patterns:

let Statements

// Valid - irrefutable pattern
let x = 5

// Valid - irrefutable tuple destructuring
let (x, y) = (1, 2)

// Invalid - refutable pattern in let statement
// This would not compile:
// let Some(x) = value  // error: refutable pattern

// Must use if let instead:
if let Some(x) = value {
    println!("Got: \(x)")
}

Function Parameters

Function parameters require irrefutable patterns because the function must be prepared to handle any value passed to it:

// Valid - irrefutable parameter
fn printPoint((x, y): (Int, Int)) {
    println!("x: \(x), y: \(y)")
}

// Invalid - refutable pattern in function parameter
// fn printPoint(Some(x): Int?) {  // error: refutable pattern
//     println!("x: \(x)")
// }

// If you need to work with a refutable pattern, handle it inside the function:
fn processValue(value: Int?) {
    if let x = value {
        println!("Got: \(x)")
    }
}

Refutable Patterns Allowed

In these contexts, you can use refutable patterns:

match Arms

match expressions are designed to handle refutable patterns. In fact, if you use an irrefutable pattern as the first arm, the compiler will warn you that subsequent arms are unreachable:

match value {
    Some(x) -> println!("Got: \(x)"),  // refutable pattern - OK
    null -> println!("No value"),       // refutable pattern - OK
}

match value {
    // Warning: irrefutable pattern as first arm makes other arms unreachable
    x -> println!("Got: \(x)"),
    Some(y) -> println!("Got y: \(y)"),  // This code is unreachable!
}

if let Expressions

if let is specifically designed for refutable patterns:

if let Some(x) = value {
    println!("Got: \(x)")
}

if let Coin.Quarter(state) = coin {
    println!("State: \(state:?)")
}

while let Loops

while let loops continue as long as a refutable pattern matches:

var stack: Vec<Int> = vec![1, 2, 3]

while let value = stack.pop() {
    println!("Popped: \(value)")
}

Understanding Refutability Through Examples

Example 1: Pattern That Might Fail

fn processOptional(value: Int?) {
    // This would be an error:
    // let Some(x) = value  // error: refutable pattern in let statement

    // Must use if let instead:
    if let x = value {
        println!("Got value: \(x)")
    } else {
        println!("Value was null")
    }
}

The pattern Some(x) is refutable because the value might be null. Using if let is the correct way to handle this.

Example 2: Pattern That Always Matches

fn processAny(value: Int) {
    // This is fine - the pattern will always match:
    let x = value
    println!("Value: \(x)")
}

The pattern x is irrefutable because any value can bind to a variable.

Example 3: Guard Conditions Make Patterns Refutable

A pattern with a guard condition is refutable:

fn checkNumber(n: Int) {
    // This is refutable because the guard might not match:
    // let x if x > 5 = n  // error: refutable pattern in let

    // Use match instead:
    match n {
        x if x > 5 -> println!("Greater than 5"),
        x if x < 0 -> println!("Negative"),
        x -> println!("Between -1 and 5"),
    }
}

Example 4: Multiple Patterns in match

match coin {
    Coin.Penny -> println!("One cent"),
    Coin.Nickel -> println!("Five cents"),
    Coin.Dime -> println!("Ten cents"),
    Coin.Quarter -> println!("Twenty-five cents"),
}

Each pattern is refutable (the value might be a different variant), but together they cover all possibilities.

Common Mistakes and How to Fix Them

Mistake 1: Using a Refutable Pattern in let

// Error: refutable pattern
// let Some(x) = maybeValue

// Fix: Use if let
if let x = maybeValue {
    println!("Got: \(x)")
}

Mistake 2: Unreachable Code in match

// Warning: this code is unreachable
match value {
    x -> println!("Any value"),      // matches everything
    Some(y) -> println!("Some: \(y)"), // unreachable!
}

// Fix: put the catch-all pattern last
match value {
    Some(y) -> println!("Some: \(y)"),
    _ -> println!("Any other value"),
}

Mistake 3: Forgetting to Handle the Failure Case

// If you only care about one case, use if let:
if let Coin.Quarter(state) = coin {
    println!("State: \(state:?)")
}

// Not match without handling other cases:
// match coin {
//     Coin.Quarter(state) -> println!("State: \(state:?)"),
//     // error: missing patterns
// }

Best Practices

  1. Use let for irrefutable patterns - When you know a pattern will always match
  2. Use if let for single refutable patterns - When you only care about one case
  3. Use match for multiple refutable patterns - When you need to handle several cases
  4. Always handle the failure case - Either with if let ... else or with exhaustive match
  5. Use guards carefully - Remember that guards make patterns refutable

Understanding refutability helps you write safer, more expressive code while leveraging the Oxide compiler's ability to catch mistakes at compile time rather than runtime.