Rust Enums
A struct says "this thing has a name and an age and marks". An enum says "this thing is either this or that or something else". Traffic lights are red, yellow or green. A payment is by cash, card or UPI. A direction is north, south, east or west. Whenever a value can be exactly one of a fixed list of possibilities, an enum is the right tool.
A simple enum
enum TrafficLight {
Red,
Yellow,
Green,
}
fn advice(light: TrafficLight) -> &'static str {
match light {
TrafficLight::Red => "Stop",
TrafficLight::Yellow => "Get ready",
TrafficLight::Green => "Go",
}
}
fn main() {
println!("{}", advice(TrafficLight::Red));
println!("{}", advice(TrafficLight::Yellow));
println!("{}", advice(TrafficLight::Green));
}Stop Get ready Go
The possibilities, Red, Yellow and Green, are called variants. You write them with the enum's name, two colons and the variant: TrafficLight::Red. The &'static str return type means "text that lives for the whole program", which is what text written in quotes is. The lifetimes page explains that funny 'static mark.
match and enums are best friends
Remember that match must handle every possible value? With enums, that rule becomes a superpower. If you later add a fourth variant, say FlashingYellow, every match that forgot about it stops compiling and points at itself. You can never forget to handle a new case.
Variants can carry data
This is where Rust enums leave most other languages behind. Each variant can hold its own data, of its own shape:
enum Payment {
Cash,
Card { last_digits: u16 },
Upi(String),
}
fn describe(p: &Payment) -> String {
match p {
Payment::Cash => String::from("paid in cash"),
Payment::Card { last_digits } => format!("paid by card ending {}", last_digits),
Payment::Upi(id) => format!("paid by UPI to {}", id),
}
}
fn main() {
let payments = vec![
Payment::Upi(String::from("shop@bank")),
Payment::Cash,
Payment::Card { last_digits: 4821 },
];
for p in &payments {
println!("Order {}", describe(p));
}
}Order paid by UPI to shop@bank Order paid in cash Order paid by card ending 4821
Cash carries nothing, Card carries a named field like a struct, and Upi carries a String like a tuple. In each match arm, the pattern unpacks the data into a variable you can use right away.
Methods on enums
Enums can have impl blocks too, exactly like structs:
enum Shape {
Circle(f64),
Square(f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => 3.14 * r * r,
Shape::Square(side) => side * side,
}
}
}
fn main() {
let shapes = [Shape::Circle(1.0), Shape::Square(3.0)];
for s in &shapes {
println!("Area: {:.2}", s.area());
}
}Area: 3.14 Area: 9.00
Two enums from the standard library are so important that they get their own pages next: Option and Result.
Practice exercises
- Make an enum
Directionwith four variants and a function that returns the opposite direction. - Add a
Triangle(f64, f64)variant (base and height) toShapeand see the compiler point at thematchuntil you handle it. - Make an enum
Messagewith variantsQuit,Write(String)andMove { x: i32, y: i32 }, and print each one withmatch.