Enums in Rust are powerful constructs that allow you to define a type that can be one of several variants.
They are particularly useful for representing choices, states, or distinct types of data under one umbrella.
What You’ll Learn
1. What Are Enums?
Enums (short for enumerations) define a type by listing its possible variants. Each variant can optionally hold additional data.
Syntax
enum EnumName { Variant1, Variant2, Variant3, }
2. Defining and Using Enums
Enums can have multiple variants, and you can create an instance of an enum by specifying a variant.
Example: Basic Enum
enum Direction { North, South, East, West, } fn main() { let direction = Direction::North; match direction { Direction::North => println!("Heading North!"), Direction::South => println!("Heading South!"), Direction::East => println!("Heading East!"), Direction::West => println!("Heading West!"), } }
3. Enums with Data
Variants in an enum can hold data, which makes enums incredibly versatile.
Example: Enum with Different Types of Data
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } fn main() { let msg = Message::Move { x: 10, y: 20 }; match msg { Message::Quit => println!("Quit message"), Message::Move { x, y } => println!("Move to ({}, {})", x, y), Message::Write(text) => println!("Write message: {}", text), Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b), } }
4. Matching with Enums
Pattern matching using match is the most common way to work with enums.
Example: Matching Enum Variants
enum Shape { Circle(f64), Rectangle(f64, f64), Square(f64), } fn area(shape: Shape) -> f64 { match shape { Shape::Circle(radius) => std::f64::consts::PI * radius * radius, Shape::Rectangle(width, height) => width * height, Shape::Square(side) => side * side, } } fn main() { let circle = Shape::Circle(5.0); let rect = Shape::Rectangle(4.0, 6.0); let square = Shape::Square(3.0); println!("Circle area: {:.2}", area(circle)); println!("Rectangle area: {:.2}", area(rect)); println!("Square area: {:.2}", area(square)); }
5. Enums and Methods
You can implement methods for enums using impl.
Example: Adding Methods to Enums
enum TrafficLight { Red, Yellow, Green, } impl TrafficLight { fn duration(&self) -> u32 { match self { TrafficLight::Red => 60, TrafficLight::Yellow => 5, TrafficLight::Green => 30, } } } fn main() { let light = TrafficLight::Red; println!("Red light duration: {} seconds", light.duration()); }
6. Option Enum
The Option enum represents an optional value and is built into Rust. It is widely used for situations where a value can be present or absent.
Definition
enum Option<T> { Some(T), None, }
Example: Using Option
fn divide(a: i32, b: i32) -> Option<i32> { if b == 0 { None } else { Some(a / b) } } fn main() { match divide(10, 2) { Some(result) => println!("Result: {}", result), None => println!("Cannot divide by zero"), } }
7. Result Enum
The Result enum is used for error handling. It represents either a success (Ok) or an error (Err).
Definition
enum Result<T, E> { Ok(T), Err(E), }
Example: Using Result
fn read_file(filename: &str) -> Result<String, String> { if filename == "valid.txt" { Ok(String::from("File content")) } else { Err(String::from("File not found")) } } fn main() { match read_file("invalid.txt") { Ok(content) => println!("File content: {}", content), Err(error) => println!("Error: {}", error), } }
8. Practical Examples
8.1 Enum for HTTP Status Codes
enum HttpStatus { Ok(u16), NotFound(u16), InternalServerError(u16), } fn main() { let status = HttpStatus::Ok(200); match status { HttpStatus::Ok(code) => println!("Success with code: {}", code), HttpStatus::NotFound(code) => println!("Not Found with code: {}", code), HttpStatus::InternalServerError(code) => println!("Server error with code: {}", code), } }
8.2 Enum for File Operations
enum FileOperation { Open(String), Close(String), Read(String, usize), Write(String, String), } fn perform_operation(op: FileOperation) { match op { FileOperation::Open(file) => println!("Opening file: {}", file), FileOperation::Close(file) => println!("Closing file: {}", file), FileOperation::Read(file, size) => println!("Reading {} bytes from file: {}", size, file), FileOperation::Write(file, content) => println!("Writing to file: {}\nContent: {}", file, content), } } fn main() { let op = FileOperation::Open(String::from("example.txt")); perform_operation(op); }
8.3 Enum for Geometry
enum Geometry { Point(f64, f64), Line(f64, f64, f64, f64), Circle(f64, f64, f64), } fn describe(geo: Geometry) { match geo { Geometry::Point(x, y) => println!("Point at ({}, {})", x, y), Geometry::Line(x1, y1, x2, y2) => println!("Line from ({}, {}) to ({}, {})", x1, y1, x2, y2), Geometry::Circle(x, y, r) => println!("Circle at ({}, {}) with radius {}", x, y, r), } } fn main() { let point = Geometry::Point(1.0, 2.0); let line = Geometry::Line(0.0, 0.0, 3.0, 4.0); let circle = Geometry::Circle(5.0, 5.0, 10.0); describe(point); describe(line); describe(circle); }
8.4 State Machine Using Enums
enum State { Start, Processing, Done, } fn transition(state: State) -> State { match state { State::Start => State::Processing, State::Processing => State::Done, State::Done => State::Start, } } fn main() { let mut state = State::Start; for _ in 0..5 { println!("Current state: {:?}", state); state = transition(state); } }
9. Summary
Key Features of Enums
- Enums group related variants into a single type.
- Variants can hold data, making enums versatile.
- Use pattern matching with match for exhaustive handling.
- Rust’s built-in Option and Result enums simplify optional values and error handling.
Best Practices
- Use enums to represent mutually exclusive states or choices.
- Combine enums with match to write clean and safe code.
- Add methods to enums using impl for reusable functionality.