Rust Enums and Pattern Matching
An enum (short for enumeration) defines a type that can be one of several named options. Each option is called a variant. Enums are one of Rust's most powerful features — they can carry data and work perfectly with pattern matching.
Defining an Enum
enum Direction {
North,
South,
East,
West,
}
A value of type Direction can only be one of these four options at any time.
fn main() {
let heading = Direction::North;
match heading {
Direction::North => println!("Going north"),
Direction::South => println!("Going south"),
Direction::East => println!("Going east"),
Direction::West => println!("Going west"),
}
}
Output:
Going north
The Traffic Light Diagram
enum Light { Red, Yellow, Green }
Light::Red → "Stop"
Light::Yellow → "Slow down"
Light::Green → "Go"
A traffic light is always exactly one of these three states.
Enums with Data
Each variant can carry its own data. Different variants can carry different types and amounts of data:
enum Shape {
Circle(f64), ← One value: radius
Rectangle(f64, f64), ← Two values: width, height
Triangle, ← No data
}
fn area(shape: Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle => 0.0,
}
}
Pattern Matching with match
The match expression compares a value against a list of patterns and runs the code for the first pattern that matches. Every possible case must be handled.
fn describe_number(n: i32) {
match n {
1 => println!("One"),
2 | 3 => println!("Two or three"),
4..=9 => println!("Between 4 and 9"),
_ => println!("Something else"), ← Catch-all
}
}
Binding Values in Patterns
Extract data from an enum variant while matching it:
enum Coin {
Penny,
Quarter(String), ← Carries a state name
}
fn value(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Quarter(state) => {
println!("Quarter from {}.", state);
25
}
}
}
The Catch-All Pattern
Use _ as the last arm to catch all values you do not explicitly handle:
match number {
1 => println!("One"),
2 => println!("Two"),
_ => println!("Other"), ← Handles 3, 4, 5, and everything else
}
Rust requires that every possible value is handled. If you miss a case, the compiler will tell you.
if let — Match a Single Variant
When you only care about one variant and want to ignore the rest, if let is a shorter alternative to match:
let shape = Shape::Circle(5.0);
if let Shape::Circle(r) = shape {
println!("Circle with radius {}", r);
}
This is identical to a match with one arm and a _ catch-all, but written more concisely.
if let with else
if let Shape::Circle(r) = shape {
println!("It is a circle with radius {}", r);
} else {
println!("It is not a circle");
}
Enums in the Standard Library
Rust's standard library uses enums heavily. You will see Option and Result in almost every Rust program. Both are enums. Learning enums well prepares you to understand those types fully in later topics.
Common Enum Patterns
Pattern Use Case ------- -------- Simple variants State machines, categories, directions Variants with data Events with payloads, shapes with dimensions match + all variants Handle every case explicitly match + _ Handle some cases, ignore the rest if let Handle one specific case only
Quick Reference
enum Name { A, B, C } ← Define an enum
let x = Name::A; ← Create a value
match x { Name::A => ..., _ => ... } ← Match all cases
if let Name::A = x { ... } ← Match one case
enum Msg { Quit, Move { x: i32 } } ← Variants with data
