match
You may be wondering—what can you actually do with an enum?
The most common operation is to match on it.
enum Status {
ToDo,
InProgress,
Done
}
impl Status {
fn is_done(&self) -> bool {
match self {
Status::Done => true,
// The `|` operator lets you match multiple patterns.
// It reads as "either `Status::ToDo` or `Status::InProgress`".
Status::InProgress | Status::ToDo => false
}
}
}
A match
statement that lets you compare a Rust value against a series of patterns.
You can think of it as a type-level if
. If status
is a Done
variant, execute the first block;
if it's a InProgress
or ToDo
variant, execute the second block.
Exhaustiveness
There's one key detail here: match
is exhaustive. You must handle all enum variants.
If you forget to handle a variant, Rust will stop you at compile-time with an error.
E.g. if we forget to handle the ToDo
variant:
match self {
Status::Done => true,
Status::InProgress => false,
}
the compiler will complain:
error[E0004]: non-exhaustive patterns: `ToDo` not covered
--> src/main.rs:5:9
|
5 | match status {
| ^^^^^^^^^^^^ pattern `ToDo` not covered
This is a big deal!
Codebases evolve over time—you might add a new status down the line, e.g. Blocked
. The Rust compiler
will emit an error for every single match
statement that's missing logic for the new variant.
That's why Rust developers often sing the praises of "compiler-driven refactoring"—the compiler tells you
what to do next, you just have to fix what it reports.
Catch-all
If you don't care about one or more variants, you can use the _
pattern as a catch-all:
match status {
Status::Done => true,
_ => false
}
The _
pattern matches anything that wasn't matched by the previous patterns.
If you're keen on correctness, avoid using catch-alls. Leverage the compiler to re-examine all matching sites and determine how new enum variants should be handled.
Exercise
The exercise for this section is located in 05_ticket_v2/02_match