Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. Rust
  4. Rust Enums Cheat Sheet
RustCheat Sheet

Rust Enums Cheat Sheet

Quick-reference for enum definitions, Option, Result, pattern matching on variants, and methods. Each section includes copy-ready snippets with inline output comments.

On this page
  1. 1Enum Definition
  2. 2Variants with Data
  3. 3Pattern Matching Enums
  4. 4Option<T> Patterns
  5. 5Result<T, E> Patterns
  6. 6Methods on Enums
  7. 7C-style Enums and Discriminants
  8. 8The matches! Macro
  9. 9Enum Size and Niche Optimization
  10. 10Enum Conversions: From and TryFrom
  11. 11Recursive Enums
Enum DefinitionVariants with DataPattern Matching EnumsOption<T> PatternsResult<T, E> PatternsMethods on EnumsC-style Enums and DiscriminantsThe matches! MacroEnum Size and Niche OptimizationEnum Conversions: From and TryFromRecursive Enums

Enum Definition

Enums define a type that can be one of several variants. Each variant is a constructor for the enum type.

Unit variants (no data)
enum Direction {
    North,
    South,
    East,
    West,
}

let dir = Direction::North;
Enum with derive macros
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Color {
    Red,
    Green,
    Blue,
}

println!("{:?}", Color::Red);  // => "Red"

Variants with Data

Each variant can carry different data: unnamed (tuple), named (struct), or none (unit).

Tuple variants (unnamed fields)
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6("::1".into());
Struct variants (named fields)
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

let s = Shape::Circle { radius: 5.0 };
Mixed variant types
enum Message {
    Quit,                         // unit variant
    Write(String),                // tuple variant
    Move { x: i32, y: i32 },     // struct variant
    ChangeColor(u8, u8, u8),     // tuple variant
}

You must pattern match to access data inside variants. No direct field access like structs.

Pattern Matching Enums

match is the primary way to use enums. The compiler enforces exhaustiveness.

Match all variants
fn describe(msg: &Message) -> String {
    match msg {
        Message::Quit => "quit".into(),
        Message::Write(text) => format!("write: {text}"),
        Message::Move { x, y } => format!("move to ({x}, {y})"),
        Message::ChangeColor(r, g, b) => format!("color: {r},{g},{b}"),
    }
}
Catch-all with _ or variable
match direction {
    Direction::North => println!("heading north"),
    _ => println!("heading somewhere else"),
}

Using _ as catch-all means the compiler will not warn when new variants are added.

if let for single variant
if let Message::Write(text) = &msg {
    println!("message: {text}");
}

Option<T> Patterns

Option<T> replaces null. Some(T) when present, None when absent. The compiler forces you to handle both.

Creating Options
let some: Option<i32> = Some(42);
let none: Option<i32> = None;
Match on Option
match some {
    Some(val) => println!("got: {val}"),
    None => println!("nothing"),
}
Common Option methods
Some(5).unwrap()              // => 5
None::<i32>.unwrap_or(0)      // => 0
Some(5).map(|x| x * 2)       // => Some(10)
None::<i32>.map(|x| x * 2)   // => None
Some(5).filter(|&x| x > 3)   // => Some(5)
Some(2).filter(|&x| x > 3)   // => None
Some(5).is_some()             // => true
None::<i32>.is_none()         // => true
if let with Option
let name: Option<&str> = Some("Alice");
if let Some(n) = name {
    println!("Hello, {n}!");
}
and_then: chain fallible operations
let port: Option<u16> = Some("8080")
    .and_then(|s| s.parse().ok());
// => Some(8080)

Result<T, E> Patterns

Result<T, E> replaces exceptions. Ok(T) for success, Err(E) for failure.

Creating Results
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("division by zero".into())
    } else {
        Ok(a / b)
    }
}
Match on Result
match divide(10, 3) {
    Ok(val) => println!("result: {val}"),
    Err(e) => println!("error: {e}"),
}
Common Result methods
Ok(5).unwrap()                    // => 5
Err::<i32, &str>("oops").unwrap_or(0)  // => 0
Ok(5).map(|x| x * 2)             // => Ok(10)
Ok::<i32, String>(5).map_err(|e| format!("err: {e}"))  // => Ok(5)
Ok(5).is_ok()                     // => true
Ok::<i32, String>(5).and_then(|x| Ok(x + 1))  // => Ok(6)
Convert Option to Result
let opt: Option<i32> = Some(5);
let result: Result<i32, &str> = opt.ok_or("missing value");
// => Ok(5)

let none: Option<i32> = None;
let result: Result<i32, &str> = none.ok_or("missing value");
// => Err("missing value")

Methods on Enums

Add methods to enums with impl blocks, just like structs.

Instance methods
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle { radius } => {
                std::f64::consts::PI * radius * radius
            }
            Shape::Rectangle { width, height } => {
                width * height
            }
        }
    }
}

let c = Shape::Circle { radius: 5.0 };
println!("{:.2}", c.area());  // => "78.54"
Constructor (associated function)
impl Shape {
    fn square(side: f64) -> Self {
        Shape::Rectangle { width: side, height: side }
    }
}

let sq = Shape::square(10.0);
Boolean check methods
impl Shape {
    fn is_circle(&self) -> bool {
        matches!(self, Shape::Circle { .. })
    }
}

The matches! macro is a concise way to check if a value matches a pattern.

C-style Enums and Discriminants

Unit-only enums can have explicit integer discriminants. Useful for FFI and bitflags.

Explicit discriminant values
#[derive(Debug)]
enum HttpStatus {
    Ok = 200,
    NotFound = 404,
    InternalError = 500,
}

println!("{}", HttpStatus::NotFound as i32);  // => 404
Auto-incrementing discriminants
enum Weekday {
    Monday = 1,
    Tuesday,    // 2
    Wednesday,  // 3
    Thursday,   // 4
    Friday,     // 5
}
Cast to integer
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum Priority {
    Low = 0,
    Medium = 1,
    High = 2,
}

let p = Priority::High;
let n: u8 = p as u8;  // => 2

#[repr(u8)] controls the memory layout. Useful for FFI compatibility.

The matches! Macro

A concise way to check if a value matches a pattern. Returns bool.

Simple pattern check
let status = HttpStatus::Ok;
let is_ok = matches!(status, HttpStatus::Ok);
// => true
With guards and multiple patterns
let num = 42;
let is_special = matches!(num, 1 | 42 | 100);
// => true

let opt = Some(5);
let is_big = matches!(opt, Some(x) if x > 3);
// => true
In filter predicates
let messages = vec![
    Message::Quit,
    Message::Write("hi".into()),
    Message::Quit,
];

let quit_count = messages.iter()
    .filter(|m| matches!(m, Message::Quit))
    .count();
// => 2

Enum Size and Niche Optimization

An enum is as large as its biggest variant (plus a discriminant tag). Rust optimizes Option<&T> to a single pointer.

Size is the largest variant + tag
use std::mem::size_of;

enum Small {
    A,            // 0 bytes of data
    B(u8),        // 1 byte of data
    C(u64),       // 8 bytes of data
}
// size_of::<Small>() => 16 (8 for u64 + 8 for tag/alignment)
Niche optimization for Option
use std::mem::size_of;

// Option<&T> is the same size as &T (8 bytes, not 16)
// Rust uses null pointer as the None discriminant
assert_eq!(size_of::<Option<&i32>>(), size_of::<&i32>());

// Option<Box<T>> is also optimized
assert_eq!(size_of::<Option<Box<i32>>>(), size_of::<Box<i32>>());

References and Box can never be null, so Rust uses null as the None niche.

Box large variants to reduce enum size
enum Data {
    Small(u32),
    Large(Box<[u8; 1024]>),  // Box reduces the variant to 8 bytes
}
// Without Box, the enum would be 1024+ bytes

Enum Conversions: From and TryFrom

Implement From for infallible conversions. Implement TryFrom for conversions that might fail.

From: infallible conversion
enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
}

impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self {
        AppError::Io(e)
    }
}

// Now ? auto-converts io::Error to AppError
TryFrom: fallible integer-to-enum
#[derive(Debug)]
enum Priority { Low, Medium, High }

impl TryFrom<u8> for Priority {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, String> {
        match value {
            0 => Ok(Priority::Low),
            1 => Ok(Priority::Medium),
            2 => Ok(Priority::High),
            _ => Err(format!("invalid priority: {value}")),
        }
    }
}

let p: Priority = 1u8.try_into().unwrap();  // => Medium

Recursive Enums

Self-referential enums require Box to give the compiler a known size.

Cons list
enum List {
    Cons(i32, Box<List>),
    Nil,
}

let list = List::Cons(1,
    Box::new(List::Cons(2,
        Box::new(List::Cons(3,
            Box::new(List::Nil)
        ))
    ))
);
Binary tree
enum Tree<T> {
    Leaf(T),
    Node {
        left: Box<Tree<T>>,
        right: Box<Tree<T>>,
    },
}

let tree = Tree::Node {
    left: Box::new(Tree::Leaf(1)),
    right: Box::new(Tree::Leaf(2)),
};

Without Box, the enum would be infinitely sized. Box gives it a fixed 8-byte pointer.

Learn Rust in Depth
Rust Pattern Matching Practice →Rust Error Handling Practice →
Warm-up1 / 2

Can you write this from memory?

Define a struct named `Point` with two `i32` fields: `x` and `y`.

See Also
Pattern Matching →Traits & Generics →

Start Practicing Rust

Free daily exercises with spaced repetition. No credit card required.

← Back to Rust Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.