Rust Enums
An enum (short for enumeration) defines a type that can be one of a fixed set of possibilities. If a struct is for “this thing has all of these fields,” an enum is for “this thing is exactly one of these options.”
Defining and using an enum
Section titled “Defining and using an enum”enum Direction { North, South, East, West,}
fn main() { let heading = Direction::North;
match heading { Direction::North => println!("Heading north"), Direction::South => println!("Heading south"), Direction::East => println!("Heading east"), Direction::West => println!("Heading west"), }}Output:
Heading northNotice match here doesn’t need a _ catch-all — because every possible Direction is listed, Rust can already tell the match is complete.
Enums can hold data
Section titled “Enums can hold data”This is where Rust’s enums go further than a typical enum in other languages: each variant can carry its own data:
enum Shape { Circle(f64), // radius Rectangle(f64, f64), // width, height}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 3.14159 * radius * radius, Shape::Rectangle(width, height) => width * height, }}
fn main() { let circle = Shape::Circle(3.0); let rectangle = Shape::Rectangle(4.0, 5.0);
println!("{:.2}", area(&circle)); println!("{:.2}", area(&rectangle));}Output:
28.2720.00Shape::Circle carries one f64 (the radius); Shape::Rectangle carries two. match pulls that data straight out for you in each arm — no separate step needed to “unwrap” it.
Enums can have methods too
Section titled “Enums can have methods too”Just like a struct, an enum can have an impl block with methods. Inside a method, you usually match self to decide what to do for each variant:
#[derive(Debug)]enum Light { Red, Yellow, Green,}
impl Light { fn seconds(&self) -> u32 { match self { Light::Red => 30, Light::Yellow => 3, Light::Green => 25, } }
fn next(&self) -> Light { match self { Light::Red => Light::Green, Light::Green => Light::Yellow, Light::Yellow => Light::Red, } }}
fn main() { let mut light = Light::Red;
for _ in 0..4 { println!("{:?} for {} seconds", light, light.seconds()); light = light.next(); }}Output:
Red for 30 secondsGreen for 25 secondsYellow for 3 secondsRed for 30 secondsnext returns a brand new Light, and light = light.next(); swaps the old one out. That’s why light is declared mut. #[derive(Debug)] works on enums the same way it does on structs, so {:?} prints the variant’s name.
Putting the logic in methods keeps it next to the type. Anyone using a Light can call light.seconds() without knowing how the timing is decided. And if you add a new variant later, like FlashingYellow, the compiler points at every match you need to update.
Option: an enum you’ve already been using
Section titled “Option: an enum you’ve already been using”The Option type you’ll meet properly on its own page is just an enum defined in the standard library:
enum Option<T> { Some(T), None,}Once enums click, Option stops looking like special syntax — it’s a completely ordinary enum with two variants, one of which happens to carry a value.
Why not just use a struct with a “type” field?
Section titled “Why not just use a struct with a “type” field?”You could imagine faking this with a struct and a string field like "circle" or "rectangle" — but then nothing stops you from typing "circl" by mistake, and you’d need to manually check which fields are actually relevant for each type. An enum makes invalid combinations impossible to write in the first place, and the compiler forces you to handle every variant.
Practice
Section titled “Practice”1. Count the coins
Section titled “1. Count the coins”Make an enum Coin with the variants Penny, Nickel, Dime and Quarter. Write fn value_in_cents(coin: &Coin) -> u32, then add up a purse of coins: a quarter, a dime, a nickel, a penny and another quarter. Hint: store the coins in an array and loop over &purse.
Show solution
enum Coin { Penny, Nickel, Dime, Quarter,}
fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }}
fn main() { let purse = [Coin::Quarter, Coin::Dime, Coin::Nickel, Coin::Penny, Coin::Quarter];
let mut total = 0; for coin in &purse { total += value_in_cents(coin); }
println!("Total: {total} cents");}Output:
Total: 66 cents2. Add a triangle
Section titled “2. Add a triangle”Take the Shape enum from this page and add a Triangle variant that holds the lengths of its three sides. Then write a perimeter function (the distance all the way around) that works for every shape. Hint: a circle’s perimeter is 2.0 * 3.14159 * radius. A triangle’s is its three sides added up.
Show solution
enum Shape { Circle(f64), Rectangle(f64, f64), Triangle(f64, f64, f64),}
fn perimeter(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 2.0 * 3.14159 * radius, Shape::Rectangle(width, height) => 2.0 * (width + height), Shape::Triangle(a, b, c) => a + b + c, }}
fn main() { let shapes = [ Shape::Circle(1.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle(3.0, 4.0, 5.0), ];
for shape in &shapes { println!("{:.2}", perimeter(shape)); }}Output:
6.2818.0012.00Try deleting the Shape::Triangle arm from the match. Rust refuses to compile, because the match no longer covers every variant. That’s the compiler making sure you never forget a case.
3. Temperatures
Section titled “3. Temperatures”Make an enum Temperature with two variants that each hold an f64: Celsius and Fahrenheit. Add a method to_celsius(&self) -> f64, and print Celsius(25.0), Fahrenheit(212.0) and Fahrenheit(50.0) in °C with one decimal place. Hint: to convert, use (f - 32.0) * 5.0 / 9.0.
Show solution
enum Temperature { Celsius(f64), Fahrenheit(f64),}
impl Temperature { fn to_celsius(&self) -> f64 { match self { Temperature::Celsius(c) => *c, Temperature::Fahrenheit(f) => (f - 32.0) * 5.0 / 9.0, } }}
fn main() { let readings = [ Temperature::Celsius(25.0), Temperature::Fahrenheit(212.0), Temperature::Fahrenheit(50.0), ];
for t in &readings { println!("{:.1} °C", t.to_celsius()); }}Output:
25.0 °C100.0 °C10.0 °CBecause self is borrowed, c and f are references (&f64). The math works on references directly, but to return the plain Celsius value you write *c, which means “the number c points to”.
- An enum defines a type as one of several named variants:
enum Direction { North, South, ... }. - Variants can carry their own data, and different variants can carry different data.
matchnaturally destructures enum data and requires every variant to be handled.- Enums can have methods in an
implblock, just like structs. Option(andResult, covered later) are just enums from the standard library — nothing magic about them.
Quick check
Section titled “Quick check”Quick check
1. What can each variant of a Rust enum do, unlike a typical enum in other languages?
2. Why doesn't the Direction match example need a catch-all _ arm?
3. What is Option<T> actually implemented as in the standard library?
Score: 0 / 3
Read more
Section titled “Read more”- Defining an Enum — The Rust Book: variants with data, methods on enums, and why Rust has
Optioninstead of null. - The
matchControl Flow Construct — The Rust Book: matching on enums in depth, with a coin-sorting example and catch-all patterns. - Enums — Rust by Example: runnable examples, including variants with named fields like
Click { x: i64, y: i64 }. - Enumerations — The Rust Reference: the full rules, such as giving variants explicit number values.
Option— standard library docs: the real definition of the enum you’ll use most, and all its helper methods.