Enums
Enums define a type that is one of several variants. Variants can hold data, making enums ideal for state machines and error models.
Recipe
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn handle(msg: Message) {
match msg {
Message::Quit => println!("quit"),
Message::Move { x, y } => println!("move {x},{y}"),
Message::Write(text) => println!("{text}"),
}
}
fn main() {
handle(Message::Write("hi".into()));
}When to reach for this: Modeling mutually exclusive states, errors, commands, and AST nodes.
Working Example
enum Traffic {
Red,
Yellow,
Green,
}
impl Traffic {
fn next(self) -> Self {
match self {
Traffic::Red => Traffic::Green,
Traffic::Green => Traffic::Yellow,
Traffic::Yellow => Traffic::Red,
}
}
}
fn main() {
let t = Traffic::Red.next();
println!("{:?}", std::mem::discriminant(&t));
}What this demonstrates:
- Unit variants like C enums
- Methods on enums via
impl - Consuming
selfinnextfor state transition API matchfor exhaustive handling
Deep Dive
Variant Forms
enum E {
Unit,
Tuple(i32, String),
Struct { id: u64 },
}Memory Layout
Rust optimizes niche enums (e.g. Option<&T> same size as &T). Not all enums are same size as largest variant due to optimizations.
Option and Result
enum Option<T> { None, Some(T) }
enum Result<T, E> { Ok(T), Err(E) }See dedicated page for idiomatic use.
Gotchas
- Non-exhaustive
match- New variant breaks downstream. Fix: Treat as API semver event; use#[non_exhaustive]for extensibility. - Large variant bloat - Enum size is at least largest variant (before optimization). Fix: Box large payload:
Large(Box<Data>). - Boolean enum smell -
enum Flag { Yes, No }worse thanboolunless more states coming. Fix: Usebooluntil variants multiply. - Derive
Eqon float payload - Not available. Fix: Use integer representation or avoidEq. - Public enum with internal invariants - Callers construct invalid combos. Fix: Private fields with constructors validating state.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Struct + bool flag | Two states only | Many transitions or typed payloads |
| Trait objects | Open set of types | Closed set - enum is faster |
| Integer codes | FFI/C interop | Safe Rust domain modeling |
bitflags crate | Many boolean flags packed | Semantic variants matter |
FAQs
Can enum variants hold different types?
Yes. Each variant defines its own payload shape.
How big is an enum?
At least the largest variant; niche optimization may shrink some (e.g. Option).
Can I implement traits on enums?
Yes with impl Trait for MyEnum.
What is `#[non_exhaustive]`?
External crates cannot match exhaustively - forward-compatible enum evolution.
Enum vs sealed trait hierarchy?
Enum: closed known set. Trait: extensible implementations across crates.
Can variants be generic?
Enums cannot be generic per variant, but enum itself can be enum E<T> { Some(T), None } style - use Option.
Null pointer optimization?
Option<NonNull<T>> and similar use niche values for None.
C-like repr?
#[repr(C)] for FFI layout control - see Unsafe/FFI section.
Naming variants?
PascalCase for variants, SCREAMING_SNAKE for enum name is not used - enum is PascalCase too.
Exhaustive testing?
Add variant -> compiler lists all match sites to update - feature not bug.
Related
- Pattern Matching with match -
matchdepth - Option & Result as Enums - core enums
- Enums for State Machines - transitions
- Deriving Traits -
#[derive(...)]
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.