Step 2 of 7
match: exhaustive by design
A match expression has the shape match value { pattern => expression, ... }. Each pattern => expression line is an arm. Rust tries the arms from top to bottom and evaluates the first one whose pattern fits. Since match is an expression, it has a value, so every arm must produce the same type.
#[derive(Debug)]
enum OrderStatus {
Pending,
Paid,
Shipped,
Delivered,
}
fn advance(status: OrderStatus) -> OrderStatus {
match status {
OrderStatus::Pending => OrderStatus::Paid,
OrderStatus::Paid => OrderStatus::Shipped,
OrderStatus::Shipped => OrderStatus::Delivered,
OrderStatus::Delivered => OrderStatus::Delivered,
}
}
fn main() {
let mut status = OrderStatus::Pending;
for _ in 0..4 {
print!("{:?} -> ", status);
status = advance(status);
}
println!("{:?}", status);
}
Pending -> Paid -> Shipped -> Delivered -> Delivered
This is a tiny state machine: a value that is always in exactly one state, plus rules for moving between states. Enums and match are the natural way to write one in Rust.
Exhaustiveness
A match must handle every possible value. Forget a variant and the program doesn't compile:
enum Light {
Red,
Yellow,
Green,
}
fn seconds(light: Light) -> u32 {
match light {
Light::Red => 30,
Light::Green => 25,
}
}
fn main() {
println!("{}", seconds(Light::Yellow));
}
error[E0004]: non-exhaustive patterns: `Light::Yellow` not covered
This is one of Rust's best features at work. Six months from now someone adds Light::FlashingRed, and the compiler lists every match in the codebase that doesn't handle it yet. In many languages, that change would compile fine and misbehave in production.
The catch-all _
The pattern _ matches anything. Put it last to mean "every other value". You need it when the possible values can't all be listed, like numbers, characters or strings. A plain name instead of _ also matches anything, and gives you the value:
fn respond(command: &str) -> String {
match command {
"start" => String::from("starting engine"),
"stop" => String::from("stopping"),
other => format!("unknown command: {}", other),
}
}
fn dice_word(n: u32) -> &'static str {
match n {
1 => "one",
6 => "six!",
_ => "something in between",
}
}
fn main() {
println!("{}", respond("start"));
println!("{}", respond("jump"));
println!("{} / {}", dice_word(6), dice_word(3));
}
starting engine
unknown command: jump
six! / something in between
(&'static str means "a string literal baked into the program". The lifetimes module explains the 'static part; for now, read it as "returns a literal".)
For your own enums, prefer listing every variant over _. A _ arm silently swallows variants added later, which switches off exactly the help described above.
Common mistakes
- Arms with different types:
1 => "one", 2 => 2giveserror[E0308]: `match` arms have incompatible types. _too early: arms are tried in order, so arms after a_can never run. The compiler warns:unreachable pattern.- Using
matchfor a yes/no question about one value:if x == 3is clearer.matchshines when there are several cases.
Your task
Write fn next(light: Light) -> Light for a traffic light: red turns green, green turns yellow, yellow turns red. List every variant: no _.
Challenge 2
Write fn shortcut(key: char) -> String for a text editor: 's' gives "save", 'o' gives "open", 'q' gives "quit", and any other key gives "no shortcut for X" with the key in place of X.
Challenge 3
Write the state machine of a vending machine: fn on_event(state: Vend, event: &str) -> Vend.
Idle+"coin"becomesHasCoinHasCoin+"push"becomesDispensingHasCoin+"refund"becomesIdleDispensing+"done"becomesIdle- any other event leaves the state as it is.
Match on the state, and decide on the event inside each arm (with if or a second match).
Previous: Enums: one of a fixed set of variants Next: Variants that carry data