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.
Enum Definition
Enums define a type that can be one of several variants. Each variant is a constructor for the enum type.
enum Direction {
North,
South,
East,
West,
}
let dir = Direction::North;#[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).
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6("::1".into());enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
let s = Shape::Circle { radius: 5.0 };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.
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}"),
}
}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 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.
let some: Option<i32> = Some(42);
let none: Option<i32> = None;match some {
Some(val) => println!("got: {val}"),
None => println!("nothing"),
}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() // => truelet name: Option<&str> = Some("Alice");
if let Some(n) = name {
println!("Hello, {n}!");
}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.
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("division by zero".into())
} else {
Ok(a / b)
}
}match divide(10, 3) {
Ok(val) => println!("result: {val}"),
Err(e) => println!("error: {e}"),
}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)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.
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"impl Shape {
fn square(side: f64) -> Self {
Shape::Rectangle { width: side, height: side }
}
}
let sq = Shape::square(10.0);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.
#[derive(Debug)]
enum HttpStatus {
Ok = 200,
NotFound = 404,
InternalError = 500,
}
println!("{}", HttpStatus::NotFound as i32); // => 404enum Weekday {
Monday = 1,
Tuesday, // 2
Wednesday, // 3
Thursday, // 4
Friday, // 5
}#[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.
let status = HttpStatus::Ok;
let is_ok = matches!(status, HttpStatus::Ok);
// => truelet 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);
// => truelet messages = vec![
Message::Quit,
Message::Write("hi".into()),
Message::Quit,
];
let quit_count = messages.iter()
.filter(|m| matches!(m, Message::Quit))
.count();
// => 2Enum 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.
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)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.
enum Data {
Small(u32),
Large(Box<[u8; 1024]>), // Box reduces the variant to 8 bytes
}
// Without Box, the enum would be 1024+ bytesEnum Conversions: From and TryFrom
Implement From for infallible conversions. Implement TryFrom for conversions that might fail.
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#[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(); // => MediumRecursive Enums
Self-referential enums require Box to give the compiler a known size.
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)
))
))
);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.
Can you write this from memory?
Define a struct named `Point` with two `i32` fields: `x` and `y`.