“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.
// `Copy` lets you pass the same `HttpStatus` value to multiple
// functions without it being moved on the first call. Plain enums
// like this one (no `String`, no `Vec`, no other heap data) are
// always cheap to copy, so deriving `Copy` (and `Clone`) costs you
// nothing and removes a borrow-checker speed bump.
#[derive(Debug, PartialEq, Clone, Copy)]
enum HttpStatus {
Ok,
NotFound,
Unauthorized,
InternalServerError,
BadRequest,
}
/// Returns the HTTP status code number for the given status.
fn status_code(status: HttpStatus) -> u16 {
match status {
HttpStatus::Ok => 200,
HttpStatus::NotFound => 404,
HttpStatus::Unauthorized => 401,
HttpStatus::InternalServerError => 500,
HttpStatus::BadRequest => 400,
}
}
#[test]
fn test_status_code() {
assert_eq!(status_code(HttpStatus::Ok), 200);
assert_eq!(status_code(HttpStatus::NotFound), 404);
assert_eq!(status_code(HttpStatus::InternalServerError), 500);
}
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.
// `Copy` lets you pass the same `HttpStatus` value to multiple
// functions without it being moved on the first call. Plain enums
// like this one (no `String`, no `Vec`, no other heap data) are
// always cheap to copy, so deriving `Copy` (and `Clone`) costs you
// nothing and removes a borrow-checker speed bump.
#[derive(Debug, PartialEq, Clone, Copy)]
enum HttpStatus {
Ok,
NotFound,
Unauthorized,
InternalServerError,
BadRequest,
}
/// Returns `true` if the request should be retried.
///
/// Only retry on server errors, not client errors.
fn should_retry(status: HttpStatus) -> bool {
matches!(status, HttpStatus::InternalServerError)
}
#[test]
fn test_should_retry() {
assert_eq!(should_retry(HttpStatus::InternalServerError), true);
assert_eq!(should_retry(HttpStatus::NotFound), false);
assert_eq!(should_retry(HttpStatus::BadRequest), false);
assert_eq!(should_retry(HttpStatus::Ok), false);
}
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.