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.