iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…