“Dad, why is my sister’s name Rose?”
“Because your mother loves roses.”
“Thanks, dad!”
“No problem, Rust enums.”
Dad is right, enums are the best. If you know the crippled form of enums in other languages (cough C), I'm so sorry for you. In Rust, they are a pure delight to work with.
But first things first: an enum is a type whose value is one of a fixed set of variants. Think
of it as a "this or that or that" type.
enum HttpStatus {
Ok,
NotFound,
InternalServerError,
}
You typically inspect an enum value with match. The compiler insists you
handle every variant, which is one of Rust's most loved features: when you
add a new variant later, every match that didn't account for it stops
compiling and tells you exactly where to update.
fn code(status: HttpStatus) -> u16 {
match status {
HttpStatus::Ok => 200,
HttpStatus::NotFound => 404,
HttpStatus::InternalServerError => 500,
}
}
Each arm of a match is pattern => expression. Multiple patterns can
share an arm with |, and the catch-all is _:
match code {
200 | 201 | 204 => "success",
404 => "missing",
_ => "something else",
}
#[derive(...)]: free implementationsYou'll see this line on many types in Rust:
#[derive(Debug, PartialEq)]
enum HttpStatus {
Ok,
NotFound,
InternalServerError,
}
The #[...] syntax is an attribute: extra instructions for the
compiler attached to the item below. derive is the most common one.
It says "please write the boilerplate for these capabilities for me."
Each name inside the parentheses is a trait (Rust's name for a
shared interface, similar to a Java interface or a Haskell type
class; traits get their own chapter later).
The two we use right away:
Debug lets you print the value with the {:?} formatter, so
println!("{status:?}") prints NotFound instead of refusing to
compile. Useful in dbg!, assert_eq! failure messages, and quick
log lines.PartialEq generates == and !=. Without it, comparing two
HttpStatus values is a compile error; with it, status == HttpStatus::Ok just works, and assert_eq! in tests can compare
whole enum values.Derive works on enums and structs whose fields all implement the
same traits. The compiler writes the obvious implementation. For
PartialEq on an enum, that means "two values are equal if they're
the same variant with equal payloads." You can always write the
implementation by hand instead when you need different behaviour.
Your first match: turn each HttpStatus variant into the numeric
code it represents. The compiler will complain if you forget a
variant, which is exactly what you want.
Useful from the standard library
- The Rust Book on
matchis the reference for pattern syntax: literal patterns,|for multiple patterns, ranges, and the_catch-all.std::cmp::PartialEqis the trait that lets you use==on a value.#[derive(PartialEq)]on the enum asks the compiler to write the implementation for you, which is what makesassert_eq!in the tests work.std::fmt::Debugenables{:?}formatting. Handy when a test fails and you want to see which variant came back.
Sometimes you only care about a single variant. You can still write a
full match with a _ catch-all arm, or you can reach for the
matches! macro.
Both are idiomatic.
Here, only a server-side failure (InternalServerError) is worth
retrying. Client errors like NotFound or BadRequest mean the
request itself is wrong, so retrying won't help.
Useful from the standard library
std::matches!expands to amatchthat returnstruefor the given pattern andfalseotherwise. Reads naturally asmatches!(status, HttpStatus::InternalServerError).- The
==operator works on enums that derivePartialEq, sostatus == HttpStatus::InternalServerErroris equally fine. Pick whichever reads better at the call site.
You defined an enum with a fixed set of variants, mapped each variant
to a value with a match, and used matches! to ask a yes/no
question about a single variant.
What we learned
- An
enumis a "this or that or that" type. Each value is exactly one of its variants, and the compiler tracks which one.matchchecks a value against patterns top to bottom and runs the first arm that fits. Every arm ispattern => expression, and the wholematchis itself an expression that produces a value.matchis exhaustive: leave a variant unhandled and the compiler refuses to build. Add a new variant later and everymatchthat needs updating tells you exactly where.|lets multiple patterns share an arm (200 | 201 | 204 => ...), and_is the catch-all when you want to ignore the rest.#[derive(Debug, PartialEq)]is the usual pair on a plain enum: one for{:?}printing, one for==. AddClone, Copywhen the variants carry no heap data so values can be passed around freely.- For a single-variant check,
matches!(value, Variant)is the compact form;value == Variantworks equally well whenPartialEqis derived.