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?
- A. Yes;
matchmay ignore uncommon variants. - B. No;
Sayis not covered. - C. No; enum variants cannot hold fields.
- D. Yes; unmatched variants return an empty string.
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?
- A.
match meter { Meter::Reading(n) => n } - B.
match meter { Meter::Reading(n) => n, _ => 0 } - C.
if meter == Meter::Offline { 0 } else { meter.0 } - D.
match meter { _ => "0" }
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.