Enums
Rust enums are tagged unions. Each variant can carry data of a different shape; match is exhaustive. Combined with generics, they replace null, error codes, and visitor patterns.
Variants, match, Option/Result, methods
EXAMPLE
// 1) Basic enum
enum Direction {
North,
East,
South,
West,
}
fn step(d: Direction) -> (i32, i32) {
match d {
Direction::North => (0, 1),
Direction::East => (1, 0),
Direction::South => (0, -1),
Direction::West => (-1, 0),
}
}
// 2) Variants with data — each variant a different shape
enum Shape {
Circle(f64), // tuple variant
Rectangle { w: f64, h: f64 }, // struct variant
Triangle(f64, f64, f64),
Point, // no data
}
fn area(s: Shape) -> f64 {
match s {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle { w, h } => w * h,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
Shape::Point => 0.0,
}
}
// 3) Methods on enums
impl Shape {
fn is_round(&self) -> bool {
matches!(self, Shape::Circle(_))
}
fn describe(&self) -> String {
match self {
Shape::Circle(r) => format!("circle r={r}"),
Shape::Rectangle { w, h } => format!("rect {w}x{h}"),
Shape::Triangle(a, b, c) => format!("tri {a},{b},{c}"),
Shape::Point => "point".to_string(),
}
}
}
// 4) Option<T> — Rust's null
fn first_word(s: &str) -> Option<&str> {
s.split_whitespace().next()
}
match first_word("hello world") {
Some(w) => println!("got {w}"),
None => println!("empty"),
}
// ? operator — early-return on None
fn lookup(map: &HashMap<String, i32>, key: &str) -> Option<i32> {
let v = map.get(key)?; // returns None if missing
Some(v + 1)
}
// 5) Result<T, E> — fallible operations
use std::num::ParseIntError;
fn parse_age(s: &str) -> Result<u32, ParseIntError> {
s.parse::<u32>()
}
match parse_age("42") {
Ok(n) => println!("age={n}"),
Err(e) => eprintln!("failed: {e}"),
}
// ? on Result — propagates errors
fn read_age(s: &str) -> Result<u32, ParseIntError> {
let n = s.trim().parse::<u32>()?;
Ok(n + 1)
}
// 6) Custom error enum + From conversions
use std::io;
#[derive(Debug)]
enum AppError {
NotFound,
Io(io::Error),
Parse(ParseIntError),
Validation(String),
}
impl From<io::Error> for AppError { fn from(e: io::Error) -> Self { AppError::Io(e) } }
impl From<ParseIntError> for AppError { fn from(e: ParseIntError) -> Self { AppError::Parse(e) } }
fn load(path: &str) -> Result<u32, AppError> {
let text = std::fs::read_to_string(path)?; // io::Error → AppError
let n = text.trim().parse::<u32>()?; // ParseIntError → AppError
Ok(n)
}
// 7) State machine — types prevent invalid transitions
enum OrderState {
Cart { items: Vec<Item> },
Confirmed { items: Vec<Item>, address: Address },
Paid { items: Vec<Item>, address: Address, payment_id: String },
Shipped { tracking_no: String },
Cancelled { reason: String },
}
impl OrderState {
fn confirm(self, address: Address) -> Result<Self, &'static str> {
match self {
OrderState::Cart { items } => Ok(OrderState::Confirmed { items, address }),
_ => Err("can only confirm a cart"),
}
}
}
// Tries to confirm a Paid order → won't compile / runtime error: type-safe!
// 8) C-style enums with explicit discriminants
enum HttpStatus {
Ok = 200,
NotFound = 404,
Server = 500,
}
impl HttpStatus {
fn from_code(c: u16) -> Option<Self> {
match c {
200 => Some(HttpStatus::Ok),
404 => Some(HttpStatus::NotFound),
500 => Some(HttpStatus::Server),
_ => None,
}
}
}
// 9) #[derive] — common conformances
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Color {
Red,
Green,
Blue,
Custom { r: u8, g: u8, b: u8 },
}
// 10) Exhaustive match enforcement
fn describe(c: Color) -> &'static str {
match c {
Color::Red => "red",
Color::Green => "green",
Color::Blue => "blue",
// Adding `Custom` to the enum and forgetting to handle it → compile error
Color::Custom { .. } => "custom",
}
}
// 11) if let / while let — destructure when only one branch matters
let opt = Some(42);
if let Some(n) = opt {
println!("got {n}");
}
let mut iter = vec![1, 2, 3].into_iter();
while let Some(v) = iter.next() {
println!("{v}");
}
// 12) Best practices
// • Use enums for domain models with finite variants
// • Use Option / Result instead of null / error codes
// • Implement Display / Debug for enums you'll log
// • Prefer struct-style variants for variants with > 2 fields
// • thiserror crate for error enums in libraries; anyhow in apps
Why it matters
Rust enums + exhaustive match make “impossible states impossible.” Model your domain as enums and add a variant later — the compiler tells you every site that needs updating.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
enum Shape {
Circle(f64),
Rect { w: f64, h: f64 },
}
fn area(s: &Shape) -> f64 {
match s {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rect { w, h } => w * h,
}
}
Try it Yourself »
Discussion
Loading…