Enums with data plus exhaustive match are Rust's main tool for making invalid states unrepresentable.
An enum defines a type whose value is exactly one of several variants. Unlike enums in C or Java, each variant can carry its own data: none, a tuple of values, or named fields.
enum Shape {
Circle { radius: f64 },
Rect(f64, f64),
Empty,
}You create a value with Shape::Rect(2.0, 3.0), and you can add methods in an impl block just like with structs.
`match` compares a value against patterns from top to bottom and runs the first arm that matches. Patterns can destructure a variant and bind its data to names. match is exhaustive: if you forget a variant, the program does not compile. That is a big deal in practice: when you add a variant later, the compiler lists every match that must handle it.
The _ pattern matches anything and is the catch-all. Use it deliberately, because it also silences the helpful exhaustiveness errors when new variants are added.
match is an expression, so all arms must produce the same type, and you can assign its result directly. Arms are separated by commas; a block arm { ... } can hold several statements.
When matching on a reference (match &shape), patterns bind references to the inner data automatically, so you can inspect a value without moving out of it.
This style of data, called a sum type or tagged union, is used everywhere in Rust: messages, states of a state machine, AST nodes, and the standard Option and Result types.
Example
enum Shape {
Circle { radius: f64 },
Rect(f64, f64),
Empty,
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rect(w, h) => w * h,
Shape::Empty => 0.0,
}
}
}
fn main() {
let shapes = [Shape::Rect(2.0, 3.0), Shape::Circle { radius: 1.0 }, Shape::Empty];
for s in &shapes {
let label = match s {
Shape::Empty => "nothing",
_ => "something",
};
println!("{label}: {:.2}", s.area()); // something: 6.00 / 3.14 / nothing: 0.00
}
}Remember
- Each enum variant can carry different data
matchis exhaustive: every variant must be handled- Avoid
_catch-alls where you want the compiler to flag new variants
Watch out for
- Using a struct with many optional fields and a
kindstring where an enum would make illegal states impossible.