Enums
Enums in Rust
Enums allow you to define a type by enumerating its possible variants. They are one of Rust's most powerful features.
Basic Enums
Simple enum definition:
enum Direction {
North,
South,
East,
West,
}
fn main() {
let dir = Direction::North;
match dir {
Direction::North => println!("Going north!"),
Direction::South => println!("Going south!"),
Direction::East => println!("Going east!"),
Direction::West => println!("Going west!"),
}
}
Enums with Data
Enums can hold data:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg1 = Message::Write(String::from("Hello"));
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::ChangeColor(255, 0, 0);
process_message(msg1);
}
fn process_message(msg: Message) {
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Text: {}", text),
Message::ChangeColor(r, g, b) => println!("Color: RGB({}, {}, {})", r, g, b),
}
}
Methods on Enums
You can define methods on enums:
impl Message {
fn call(&self) {
match self {
Message::Write(text) => println!("Writing: {}", text),
_ => println!("Other message"),
}
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
The Option Enum
Rust's way of handling null values:
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
// Using Option
match some_number {
Some(n) => println!("Number: {}", n),
None => println!("No number"),
}
}
Working with Option
fn main() {
let x: Option<i32> = Some(5);
let y: Option<i32> = None;
// Using unwrap_or
println!("x: {}", x.unwrap_or(0));
println!("y: {}", y.unwrap_or(0));
// Using map
let doubled = x.map(|n| n * 2);
println!("Doubled: {:?}", doubled);
// Using and_then
let result = x.and_then(|n| {
if n > 0 {
Some(n * 2)
} else {
None
}
});
println!("Result: {:?}", result);
}
The Result Enum
For error handling:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("Problem opening file: {:?}", error);
}
};
}
Result Methods
fn divide(x: f64, y: f64) -> Result<f64, String> {
if y == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(x / y)
}
}
fn main() {
// Using match
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Using unwrap_or
let result = divide(10.0, 0.0).unwrap_or(0.0);
println!("Result with default: {}", result);
// Using ?
fn calculate() -> Result<f64, String> {
let x = divide(10.0, 2.0)?;
let y = divide(x, 2.0)?;
Ok(y)
}
}
Pattern Matching with Enums
Destructuring
enum WebEvent {
PageLoad,
PageUnload,
KeyPress(char),
Paste(String),
Click { x: i64, y: i64 },
}
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::PageUnload => println!("page unloaded"),
WebEvent::KeyPress(c) => println!("pressed '{}'", c),
WebEvent::Paste(s) => println!("pasted \"{}\"", s),
WebEvent::Click { x, y } => {
println!("clicked at x={}, y={}", x, y);
}
}
}
Guards
enum Temperature {
Celsius(f32),
Fahrenheit(f32),
}
fn main() {
let temp = Temperature::Celsius(35.0);
match temp {
Temperature::Celsius(t) if t > 30.0 => println!("Hot day!"),
Temperature::Celsius(t) => println!("Temperature: {}°C", t),
Temperature::Fahrenheit(t) => println!("Temperature: {}°F", t),
}
}
Generic Enums
enum MyOption<T> {
Some(T),
None,
}
enum MyResult<T, E> {
Ok(T),
Err(E),
}
fn main() {
let number: MyOption<i32> = MyOption::Some(5);
let text: MyOption<String> = MyOption::None;
}
Recursive Enums
Using Box
for recursive types:
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
Practical Examples
State Machine
#[derive(Debug)]
enum State {
Idle,
Running { progress: u32 },
Completed { result: String },
Failed { error: String },
}
struct Task {
state: State,
}
impl Task {
fn new() -> Self {
Task { state: State::Idle }
}
fn start(&mut self) {
self.state = State::Running { progress: 0 };
}
fn update_progress(&mut self, progress: u32) {
if let State::Running { .. } = self.state {
self.state = State::Running { progress };
}
}
fn complete(&mut self, result: String) {
self.state = State::Completed { result };
}
}
fn main() {
let mut task = Task::new();
println!("Initial: {:?}", task.state);
task.start();
println!("Started: {:?}", task.state);
task.update_progress(50);
println!("Progress: {:?}", task.state);
task.complete(String::from("Success!"));
println!("Completed: {:?}", task.state);
}
Command Pattern
enum Command {
Create { name: String },
Delete { id: u32 },
Update { id: u32, name: String },
List,
}
fn execute_command(cmd: Command) {
match cmd {
Command::Create { name } => {
println!("Creating item: {}", name);
}
Command::Delete { id } => {
println!("Deleting item with id: {}", id);
}
Command::Update { id, name } => {
println!("Updating item {} with name: {}", id, name);
}
Command::List => {
println!("Listing all items");
}
}
}
fn main() {
let commands = vec![
Command::Create { name: String::from("Task 1") },
Command::List,
Command::Update { id: 1, name: String::from("Updated Task") },
Command::Delete { id: 1 },
];
for cmd in commands {
execute_command(cmd);
}
}
Error Types
#[derive(Debug)]
enum AppError {
IoError(String),
ParseError(String),
ValidationError { field: String, message: String },
}
fn read_config() -> Result<String, AppError> {
Err(AppError::IoError(String::from("File not found")))
}
fn main() {
match read_config() {
Ok(config) => println!("Config: {}", config),
Err(e) => match e {
AppError::IoError(msg) => eprintln!("IO Error: {}", msg),
AppError::ParseError(msg) => eprintln!("Parse Error: {}", msg),
AppError::ValidationError { field, message } => {
eprintln!("Validation Error in {}: {}", field, message)
}
}
}
}
Best Practices
- Use enums for finite sets of variants
- Leverage pattern matching for exhaustive handling
- Use Option instead of null
- Use Result for error handling
- Consider using enums for state machines
- Name variants clearly and consistently
- Group related data in enum variants
- Implement useful methods on enums when appropriate