R Rust by Evidence PDF

Chapter 7 — Enums and Pattern Matching

Goal

Represent a value that is exactly one of several meaningful alternatives.

A boolean can say yes or no, but it cannot explain which kind of yes. An enum gives each case a name and may attach different data to each case.

enum Command {
    Quit,
    Move { x: i32, y: i32 },
    Say(String),
}

fn describe(command: Command) -> &'static str {
    match command {
        Command::Quit => "quit",
        Command::Move { x, y } => "move",
    }
}

Prediction

Will describe compile?

RevealB

Representative rustc evidence:

error[E0004]: non-exhaustive patterns: `Command::Say(_)` not covered
  |
  |     match command {
  |           ^^^^^^^ pattern `Command::Say(_)` not covered

Rust requires an exhaustive match. If a Command exists, one arm must handle its variant.

Mental model

Working model: an enum value carries a tag saying which variant it is, plus the data for that variant. A match reads the tag, selects one arm, and can unpack the attached data.

Precise model: every match arm is a pattern followed by an expression. Patterns can test structure, bind names, ignore parts with _, and include alternatives with |. All arms must produce compatible types because the whole match is an expression. Exhaustiveness is checked at compile time.

enum Command { Quit, Move { x: i32, y: i32 }, Say(String) }

fn describe(command: &Command) -> String {
    match command {
        Command::Quit => String::from("quit"),
        Command::Move { x, y } => format!("move to ({x}, {y})"),
        Command::Say(text) => format!("say {text:?}"),
    }
}

fn main() {
    let command = Command::Say(String::from("steady"));
    println!("{}", describe(&command));
}

Output:

say "steady"

Matching &Command means bindings such as text are references to data inside the borrowed enum. The command remains available after the call.

Use if let when only one pattern matters:

if let Command::Move { x, y } = command {
    println!("destination: {x}, {y}");
}

This deliberately ignores every other variant. That is concise, but it gives up the compiler's reminder when a new variant deserves handling.

Simplest fix

Add the missing explicit arm:

Command::Say(text) => "say",

Use the bound text if the result needs its contents; otherwise write Command::Say(_).

Tradeoff alternative: add a wildcard arm, _ => "other". It is appropriate when all remaining and future variants truly share behavior. The cost is silence: adding a new variant will not force this match to be revisited.

Mini challenge

Return the payload for Reading and zero for the other states:

enum Meter {
    Offline,
    Reading(i64),
    Fault { code: u16 },
}

fn value(meter: Meter) -> i64 {
    todo!()
}

Which implementation is both exhaustive and able to compile?

Answer

B:

enum Meter { Offline, Reading(i64), Fault { code: u16 } }

fn value(meter: Meter) -> i64 {
    match meter {
        Meter::Reading(n) => n,
        _ => 0,
    }
}

The wildcard is reasonable here because the requirement explicitly groups every non-reading state together. If faults later need logging, replace _ with named arms so the distinction becomes visible.