Skip to main content
← Gists
rust Apr 22, 2024

Exhaustive Pattern Matching in Rust

Using Rust's match expressions and enums to handle every variant at compile time. No missing cases allowed.

Rust’s match is exhaustive: the compiler enforces that every possible variant is handled. Add a new variant and every match expression that forgets it becomes a compile error.

Modeling states with enums

connection.rs
#[derive(Debug)]
enum ConnectionState {
  Disconnected,
  Connecting { attempt: u32 },
  Connected { latency_ms: u64 },
  Error { message: String },
}

fn describe(state: &ConnectionState) -> String {
  match state {
      ConnectionState::Disconnected => {
          "Not connected".to_string()
      }
      ConnectionState::Connecting { attempt } => {
          format!("Connecting (attempt #{})", attempt)
      }
      ConnectionState::Connected { latency_ms } => {
          format!("Connected ({}ms latency)", latency_ms)
      }
      ConnectionState::Error { message } => {
          format!("Error: {}", message)
      }
  }
}

Why this matters

If you add a fifth variant like ConnectionState::Reconnecting { delay_ms: u64 }, the compiler will flag every match that doesn’t handle it. This turns runtime bugs into compile-time errors.

Guards and bindings

guards.rs
fn severity(state: &ConnectionState) -> &str {
  match state {
      ConnectionState::Connected { latency_ms } if *latency_ms > 500 => "warn",
      ConnectionState::Connected { .. } => "ok",
      ConnectionState::Error { .. } => "critical",
      _ => "info",
  }
}

Guards add conditions to individual arms without losing exhaustiveness guarantees.