Introduction to Enums in Rust
Enums (enumerations) in Rust are a powerful way to define a type that can be one of several variants. Unlike enums in many other languages, Rust's enums can contain data, making them a form of algebraic data types (ADTs). They are fundamental to Rust's type system and are used extensively for error handling, state machines, and domain modeling.
Key Concepts
- Type Safety: Enums ensure that only valid variants can be used
- Pattern Matching: Enums work seamlessly with
matchexpressions - Data Carrier: Each variant can hold different types and amounts of data
- Exhaustiveness: The compiler ensures all variants are handled
- Memory Efficiency: Enums use only as much memory as the largest variant
1. Basic Enum Syntax
Simple Enums
// Basic enum without data enum Direction { North, South, East, West, } enum DayOfWeek { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, } enum HttpStatus { Ok, NotFound, InternalServerError, } fn main() { // Creating enum instances let direction = Direction::North; let day = DayOfWeek::Friday; let status = HttpStatus::Ok; // Using match with enums match direction { Direction::North => println!("Going North!"), Direction::South => println!("Going South!"), Direction::East => println!("Going East!"), Direction::West => println!("Going West!"), } // Enums can be compared with pattern matching match day { DayOfWeek::Saturday | DayOfWeek::Sunday => println!("Weekend!"), _ => println!("Weekday :("), } } Enums with Discriminants
// Enums can have explicit integer values (like C enums) enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, Yellow = 0xffff00, } enum HttpStatusCode { Ok = 200, Created = 201, Accepted = 202, BadRequest = 400, NotFound = 404, InternalServerError = 500, } fn main() { // Accessing discriminant values println!("Red value: {}", Color::Red as i32); println!("Green value: {:x}", Color::Green as i32); println!("Blue value: {:06x}", Color::Blue as i32); // Converting from integer to enum (with safety) fn status_from_code(code: u16) -> Option<HttpStatusCode> { match code { 200 => Some(HttpStatusCode::Ok), 201 => Some(HttpStatusCode::Created), 202 => Some(HttpStatusCode::Accepted), 400 => Some(HttpStatusCode::BadRequest), 404 => Some(HttpStatusCode::NotFound), 500 => Some(HttpStatusCode::InternalServerError), _ => None, } } if let Some(status) = status_from_code(200) { println!("Status code 200 means {:?}", status); } } 2. Enums with Data
Different Types of Data in Variants
// Each variant can hold different data enum Message { Quit, // No data Move { x: i32, y: i32 }, // Anonymous struct Write(String), // Single type ChangeColor(i32, i32, i32), // Tuple Echo(String), // Multiple variants can have same type Resize { width: u32, height: u32 }, } // Enum with numeric values enum Shape { Circle(f64), // radius Rectangle { width: f64, height: f64 }, Triangle(f64, f64, f64), // sides } // Enum with different data types enum WebEvent { PageLoad, KeyPress(char), Click { x: i64, y: i64 }, } fn main() { // Creating instances with data let msg1 = Message::Quit; let msg2 = Message::Move { x: 10, y: 20 }; let msg3 = Message::Write(String::from("Hello")); let msg4 = Message::ChangeColor(255, 0, 0); // Processing different variants process_message(msg1); process_message(msg2); process_message(msg3); process_message(msg4); // Working with shapes let circle = Shape::Circle(5.0); let rect = Shape::Rectangle { width: 10.0, height: 20.0 }; let triangle = Shape::Triangle(3.0, 4.0, 5.0); println!("Circle area: {}", area(circle)); println!("Rectangle area: {}", area(rect)); println!("Triangle area: {}", area(triangle)); } fn process_message(msg: Message) { match msg { Message::Quit => println!("Quitting"), Message::Move { x, y } => println!("Moving to ({}, {})", x, y), Message::Write(text) => println!("Writing: {}", text), Message::ChangeColor(r, g, b) => println!("Changing color to RGB({},{},{})", r, g, b), Message::Echo(s) => println!("Echo: {}", s), Message::Resize { width, height } => println!("Resizing to {}x{}", width, height), } } fn area(shape: Shape) -> f64 { match shape { Shape::Circle(radius) => std::f64::consts::PI * radius * radius, Shape::Rectangle { width, height } => width * height, Shape::Triangle(a, b, c) => { let s = (a + b + c) / 2.0; (s * (s - a) * (s - b) * (s - c)).sqrt() } } } Nested Enums
// Enums can contain other enums #[derive(Debug)] enum NetworkDevice { Router, Switch, Firewall, } #[derive(Debug)] enum Protocol { TCP, UDP, ICMP, } #[derive(Debug)] enum Packet { Incoming { device: NetworkDevice, protocol: Protocol, data: Vec<u8>, }, Outgoing { protocol: Protocol, destination: String, data: Vec<u8>, }, Error(String), } // Enums in collections enum Value { Integer(i32), Float(f64), Text(String), Boolean(bool), List(Vec<Value>), Dictionary(Vec<(String, Value)>), } fn main() { let packet = Packet::Incoming { device: NetworkDevice::Router, protocol: Protocol::TCP, data: vec![0x01, 0x02, 0x03], }; println!("Packet: {:?}", packet); // Complex nested enum let config = Value::Dictionary(vec![ ("name".to_string(), Value::Text("server".to_string())), ("port".to_string(), Value::Integer(8080)), ("enabled".to_string(), Value::Boolean(true)), ("tags".to_string(), Value::List(vec![ Value::Text("production".to_string()), Value::Text("critical".to_string()), ])), ]); // Process the nested structure if let Value::Dictionary(entries) = config { for (key, value) in entries { print!("{}: ", key); match value { Value::Integer(i) => println!("{}", i), Value::Float(f) => println!("{}", f), Value::Text(s) => println!("{}", s), Value::Boolean(b) => println!("{}", b), Value::List(l) => println!("{:?}", l), Value::Dictionary(_) => println!("nested dict"), } } } } 3. The Option Enum
Basic Option Usage
fn main() { // Option represents a value that may or may not exist let some_number: Option<i32> = Some(42); let some_string: Option<String> = Some(String::from("hello")); let absent_number: Option<i32> = None; // Using Option safely fn divide(numerator: f64, denominator: f64) -> Option<f64> { if denominator == 0.0 { None } else { Some(numerator / denominator) } } let result = divide(10.0, 2.0); let invalid = divide(10.0, 0.0); // Pattern matching with Option match result { Some(value) => println!("Result: {}", value), None => println!("Division by zero!"), } // if let for single pattern if let Some(value) = result { println!("Got value: {}", value); } // Option combinators let number = Some(5); // map - transform the value if it exists let doubled = number.map(|x| x * 2); println!("Doubled: {:?}", doubled); // and_then - chain operations that return Option let result = number .and_then(|x| if x > 0 { Some(x) } else { None }) .map(|x| x * 3); println!("Chained: {:?}", result); // unwrap_or - provide default let value = absent_number.unwrap_or(0); println!("Default value: {}", value); // unwrap_or_else - compute default lazily let value = absent_number.unwrap_or_else(|| { println!("Computing default..."); 42 }); println!("Computed value: {}", value); } Advanced Option Patterns
fn main() { // Option with collections let numbers = vec![1, 2, 3, 4, 5]; // Safe indexing let first = numbers.get(0); // Some(&1) let tenth = numbers.get(10); // None // Option in filtering let even_numbers: Vec<_> = numbers .iter() .filter_map(|&x| if x % 2 == 0 { Some(x) } else { None }) .collect(); println!("Even numbers: {:?}", even_numbers); // Option with structs struct User { name: String, age: Option<u32>, // Age might be unknown } let users = vec![ User { name: "Alice".to_string(), age: Some(30) }, User { name: "Bob".to_string(), age: None }, User { name: "Charlie".to_string(), age: Some(25) }, ]; // Find first user with known age > 25 let result = users.iter() .find(|user| user.age.map_or(false, |age| age > 25)); if let Some(user) = result { println!("Found user: {}", user.name); } // Chaining Option operations fn process_user_age(user: &User) -> Option<String> { user.age .filter(|&age| age >= 18) .map(|age| format!("Adult: {} years old", age)) } for user in &users { if let Some(message) = process_user_age(user) { println!("{}: {}", user.name, message); } } // Option with try operator (?) in functions fn get_first_even(numbers: &[i32]) -> Option<i32> { let first = numbers.get(0)?; // Returns None if numbers is empty if first % 2 == 0 { Some(*first) } else { numbers.get(1).copied() } } println!("First even in [1,3,5]: {:?}", get_first_even(&[1,3,5])); println!("First even in [2,4,6]: {:?}", get_first_even(&[2,4,6])); } 4. The Result Enum
Basic Result Usage
use std::fs::File; use std::io::Read; #[derive(Debug)] enum MathError { DivisionByZero, NegativeSquareRoot, Overflow, } fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("Division by zero")) } else { Ok(a / b) } } fn square_root(x: f64) -> Result<f64, &'static str> { if x < 0.0 { Err("Cannot take square root of negative number") } else { Ok(x.sqrt()) } } fn main() { // Basic Result handling let result1 = divide(10.0, 2.0); let result2 = divide(10.0, 0.0); match result1 { Ok(value) => println!("Result: {}", value), Err(e) => println!("Error: {}", e), } match result2 { Ok(value) => println!("Result: {}", value), Err(e) => println!("Error: {}", e), } // Result combinators let number = "42".parse::<i32>(); // map - transform Ok value let doubled = number.map(|x| x * 2); println!("Doubled: {:?}", doubled); // map_err - transform error let mapped_error = number.map_err(|e| format!("Parse error: {}", e)); // and_then - chain operations let result = number .and_then(|x| if x > 0 { Ok(x) } else { Err("negative") }) .map(|x| x * 2); println!("Chained result: {:?}", result); // unwrap_or - provide default for Ok let value = number.unwrap_or(0); println!("Value or default: {}", value); // unwrap_or_else - compute default lazily let value = number.unwrap_or_else(|_| { println!("Using fallback value"); 42 }); println!("Computed value: {}", value); } Advanced Result Patterns
use std::num::ParseIntError; use std::fs; #[derive(Debug)] enum AppError { Io(std::io::Error), Parse(ParseIntError), Validation(String), } impl From<std::io::Error> for AppError { fn from(error: std::io::Error) -> Self { AppError::Io(error) } } impl From<ParseIntError> for AppError { fn from(error: ParseIntError) -> Self { AppError::Parse(error) } } fn read_number_from_file(path: &str) -> Result<i32, AppError> { // The ? operator automatically converts errors using From let content = fs::read_to_string(path)?; let number = content.trim().parse::<i32>()?; if number < 0 { return Err(AppError::Validation("Number must be positive".to_string())); } Ok(number) } fn main() { // Collecting Results into collections let strings = vec!["1", "2", "abc", "3", "def"]; // Collect into Result<Vec<i32>, ParseIntError> let numbers: Result<Vec<i32>, ParseIntError> = strings .iter() .map(|s| s.parse::<i32>()) .collect(); match numbers { Ok(nums) => println!("All numbers: {:?}", nums), Err(e) => println!("Parse error: {}", e), } // Filter out errors let valid_numbers: Vec<i32> = strings .iter() .filter_map(|s| s.parse().ok()) .collect(); println!("Valid numbers: {:?}", valid_numbers); // Partition Results let (successes, failures): (Vec<_>, Vec<_>) = strings .iter() .map(|s| s.parse::<i32>()) .partition(Result::is_ok); let successes: Vec<i32> = successes.into_iter().map(Result::unwrap).collect(); let failures: Vec<ParseIntError> = failures.into_iter().map(Result::unwrap_err).collect(); println!("Successes: {:?}", successes); println!("Failures count: {}", failures.len()); // Using try_fold for early termination let numbers = vec![1, 2, 3, 4, 5]; let result = numbers.iter().try_fold(0, |acc, &x| { if x == 3 { Err("Found 3!") } else { Ok(acc + x) } }); match result { Ok(sum) => println!("Sum: {}", sum), Err(msg) => println!("Stopped: {}", msg), } // Using custom error type match read_number_from_file("number.txt") { Ok(n) => println!("Number: {}", n), Err(AppError::Io(e)) => println!("IO error: {}", e), Err(AppError::Parse(e)) => println!("Parse error: {}", e), Err(AppError::Validation(msg)) => println!("Validation: {}", msg), } } 5. Methods on Enums
Implementing Methods
#[derive(Debug)] enum TrafficLight { Red, Yellow, Green, } impl TrafficLight { // Method that returns the next state fn next(&self) -> TrafficLight { match self { TrafficLight::Red => TrafficLight::Green, TrafficLight::Green => TrafficLight::Yellow, TrafficLight::Yellow => TrafficLight::Red, } } // Method that returns duration fn duration(&self) -> u32 { match self { TrafficLight::Red => 60, TrafficLight::Yellow => 5, TrafficLight::Green => 55, } } // Method with self parameter fn description(&self) -> String { match self { TrafficLight::Red => "Stop".to_string(), TrafficLight::Yellow => "Caution".to_string(), TrafficLight::Green => "Go".to_string(), } } // Associated function (no self) fn all() -> Vec<TrafficLight> { vec![TrafficLight::Red, TrafficLight::Yellow, TrafficLight::Green] } } fn main() { let light = TrafficLight::Red; println!("Current: {:?}", light); println!("Description: {}", light.description()); println!("Duration: {} seconds", light.duration()); let next = light.next(); println!("Next: {:?}", next); println!("All lights: {:?}", TrafficLight::all()); } More Complex Method Examples
#[derive(Debug, Clone)] enum PaymentMethod { Cash, CreditCard { number: String, expiry: String, cvv: String }, PayPal { email: String }, BankTransfer { account: String, routing: String }, } impl PaymentMethod { fn is_valid(&self) -> bool { match self { PaymentMethod::Cash => true, PaymentMethod::CreditCard { number, expiry, cvv } => { // Simple validation logic number.len() == 16 && expiry.len() == 5 && cvv.len() == 3 && number.chars().all(|c| c.is_ascii_digit()) } PaymentMethod::PayPal { email } => { email.contains('@') && email.contains('.') } PaymentMethod::BankTransfer { account, routing } => { account.len() >= 8 && routing.len() == 9 && account.chars().all(|c| c.is_ascii_digit()) && routing.chars().all(|c| c.is_ascii_digit()) } } } fn fee_percentage(&self) -> f64 { match self { PaymentMethod::Cash => 0.0, PaymentMethod::CreditCard { .. } => 2.9, PaymentMethod::PayPal { .. } => 2.5, PaymentMethod::BankTransfer { .. } => 0.5, } } fn masked_display(&self) -> String { match self { PaymentMethod::Cash => "Cash".to_string(), PaymentMethod::CreditCard { number, .. } => { format!("**** **** **** {}", &number[12..]) } PaymentMethod::PayPal { email } => { let parts: Vec<&str> = email.split('@').collect(); if parts.len() == 2 { format!("{}@***.{}", &parts[0][..1], parts[1].split('.').last().unwrap_or("")) } else { "PayPal account".to_string() } } PaymentMethod::BankTransfer { .. } => "Bank transfer".to_string(), } } } fn main() { let methods = vec![ PaymentMethod::Cash, PaymentMethod::CreditCard { number: "1234567890123456".to_string(), expiry: "12/25".to_string(), cvv: "123".to_string(), }, PaymentMethod::PayPal { email: "[email protected]".to_string(), }, PaymentMethod::BankTransfer { account: "12345678".to_string(), routing: "123456789".to_string(), }, ]; for method in &methods { println!("{}", method.masked_display()); println!(" Valid: {}", method.is_valid()); println!(" Fee: {}%", method.fee_percentage()); } } 6. Enums with Generic Parameters
Generic Enums
// Generic enum #[derive(Debug)] enum MyOption<T> { Some(T), None, } // Enum with multiple generic parameters #[derive(Debug)] enum Either<L, R> { Left(L), Right(R), } // Enum with generic and specific constraints use std::fmt::Display; #[derive(Debug)] enum Result<T, E> { Ok(T), Err(E), } impl<T, E> Result<T, E> { fn is_ok(&self) -> bool { match self { Result::Ok(_) => true, Result::Err(_) => false, } } fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Result<U, E> { match self { Result::Ok(t) => Result::Ok(f(t)), Result::Err(e) => Result::Err(e), } } } // Enum with where clause use std::hash::Hash; #[derive(Debug)] enum MapEntry<K, V> where K: Hash + Eq, { Occupied { key: K, value: V }, Vacant { key: K }, } fn main() { let some_value: MyOption<i32> = MyOption::Some(42); let none_value: MyOption<String> = MyOption::None; println!("some: {:?}, none: {:?}", some_value, none_value); let either: Either<i32, String> = Either::Left(100); let either2: Either<i32, String> = Either::Right("hello".to_string()); println!("either: {:?}, either2: {:?}", either, either2); let result: Result<i32, String> = Result::Ok(42); println!("is_ok: {}, mapped: {:?}", result.is_ok(), result.map(|x| x * 2)); } Const Generics with Enums
// Enum with const generic #[derive(Debug)] enum FixedSizeArray<T, const N: usize> { Filled([T; N]), Partial(Vec<T>), } impl<T, const N: usize> FixedSizeArray<T, N> { fn new() -> Self { FixedSizeArray::Partial(Vec::new()) } fn push(&mut self, value: T) -> Result<(), String> { match self { FixedSizeArray::Filled(_) => Err("Already filled".to_string()), FixedSizeArray::Partial(vec) => { vec.push(value); if vec.len() == N { let array = std::mem::take(vec).try_into().unwrap(); *self = FixedSizeArray::Filled(array); } Ok(()) } } } } // Enum with multiple const generics #[derive(Debug)] enum Matrix<T, const ROWS: usize, const COLS: usize> { Data([[T; COLS]; ROWS]), Empty, } fn main() { let mut arr = FixedSizeArray::<i32, 3>::new(); arr.push(1).unwrap(); arr.push(2).unwrap(); arr.push(3).unwrap(); println!("Fixed array: {:?}", arr); let matrix = Matrix::<i32, 2, 3>::Data([[1, 2, 3], [4, 5, 6]]); println!("Matrix: {:?}", matrix); } 7. Enums and Pattern Matching
Advanced Pattern Matching
#[derive(Debug)] enum ComplexEnum { Unit, Tuple(i32, String), Struct { x: i32, y: i32 }, Nested(Box<ComplexEnum>), Multiple(i32, Option<String>), } fn main() { let values = vec![ ComplexEnum::Unit, ComplexEnum::Tuple(42, "hello".to_string()), ComplexEnum::Struct { x: 10, y: 20 }, ComplexEnum::Nested(Box::new(ComplexEnum::Unit)), ComplexEnum::Multiple(100, Some("data".to_string())), ]; for value in &values { match value { ComplexEnum::Unit => println!("Unit variant"), ComplexEnum::Tuple(x, s) if x > &0 => { println!("Positive tuple: {}, {}", x, s); } ComplexEnum::Tuple(x, s) => { println!("Non-positive tuple: {}, {}", x, s); } ComplexEnum::Struct { x, y } if x == y => { println!("Square at ({}, {})", x, y); } ComplexEnum::Struct { x, y } => { println!("Rectangle at ({}, {})", x, y); } ComplexEnum::Nested(inner) => { println!("Nested: {:?}", inner); // Recursive matching if let ComplexEnum::Unit = **inner { println!("Contains unit!"); } } ComplexEnum::Multiple(num, Some(s)) => { println!("Multiple with data: {}, {}", num, s); } ComplexEnum::Multiple(num, None) => { println!("Multiple without data: {}", num); } } } } Binding with @
#[derive(Debug)] enum Temperature { Celsius(i32), Fahrenheit(i32), } fn main() { let temps = vec![ Temperature::Celsius(25), Temperature::Celsius(40), Temperature::Fahrenheit(100), Temperature::Fahrenheit(32), ]; for temp in &temps { match temp { // @ binds the value to a variable while matching pattern temp @ Temperature::Celsius(t) if t > &30 => { println!("Hot day! {:?}", temp); } Temperature::Celsius(t) => { println!("Pleasant {}°C", t); } Temperature::Fahrenheit(f) if *f > 90 => { println!("Hot in Fahrenheit: {}", f); } Temperature::Fahrenheit(f) => { println!("Normal Fahrenheit: {}", f); } } } // Using @ with ranges let number = 42; match number { n @ 0..=9 => println!("Single digit: {}", n), n @ 10..=99 => println!("Two digits: {}", n), n @ 100..=999 => println!("Three digits: {}", n), n => println!("Large number: {}", n), } // @ with nested patterns enum Container { Box(Option<i32>), } let container = Container::Box(Some(42)); match container { Container::Box(Some(x @ 1..=50)) => { println!("Small number in box: {}", x); } Container::Box(Some(x)) => { println!("Large number in box: {}", x); } Container::Box(None) => { println!("Empty box"); } } } Reference Patterns
#[derive(Debug)] enum Data { Number(i32), Text(String), } fn main() { let data = Data::Text(String::from("hello")); // Match with references match &data { Data::Number(n) => println!("Number: {}", n), Data::Text(s) => println!("Text: {}", s), } // ref keyword for borrowing match data { Data::Number(ref n) => println!("Borrowed number: {}", n), Data::Text(ref s) => println!("Borrowed text: {}", s), } // data still valid because we borrowed // ref mut for mutable borrowing let mut data = Data::Number(42); match data { Data::Number(ref mut n) => { *n += 10; println!("Modified number: {}", n); } Data::Text(ref mut s) => { s.push_str(" world"); println!("Modified text: {}", s); } } println!("After match: {:?}", data); // Matching on references let data_ref = &Data::Number(100); match data_ref { &Data::Number(n) => println!("Dereferenced: {}", n), &Data::Text(ref s) => println!("Dereferenced text: {}", s), } } 8. Enums in Collections
Vectors of Enums
#[derive(Debug)] enum Command { Push(i32), Pop, Print, Add, Subtract, Multiply, Divide, } struct Stack { values: Vec<i32>, } impl Stack { fn new() -> Self { Stack { values: Vec::new() } } fn execute(&mut self, cmd: Command) -> Option<i32> { match cmd { Command::Push(x) => { self.values.push(x); None } Command::Pop => self.values.pop(), Command::Print => { println!("Stack: {:?}", self.values); None } Command::Add => { let b = self.values.pop()?; let a = self.values.pop()?; self.values.push(a + b); None } Command::Subtract => { let b = self.values.pop()?; let a = self.values.pop()?; self.values.push(a - b); None } Command::Multiply => { let b = self.values.pop()?; let a = self.values.pop()?; self.values.push(a * b); None } Command::Divide => { let b = self.values.pop()?; let a = self.values.pop()?; if b != 0 { self.values.push(a / b); } None } } } } fn main() { let program = vec![ Command::Push(5), Command::Push(3), Command::Add, Command::Print, Command::Push(2), Command::Multiply, Command::Print, Command::Pop, Command::Print, ]; let mut stack = Stack::new(); for cmd in program { stack.execute(cmd); } } HashMaps with Enums
use std::collections::HashMap; #[derive(Debug)] enum ConfigValue { String(String), Integer(i64), Float(f64), Boolean(bool), List(Vec<ConfigValue>), Map(HashMap<String, ConfigValue>), } impl ConfigValue { fn as_string(&self) -> Option<&String> { match self { ConfigValue::String(s) => Some(s), _ => None, } } fn as_integer(&self) -> Option<i64> { match self { ConfigValue::Integer(i) => Some(*i), _ => None, } } fn get(&self, key: &str) -> Option<&ConfigValue> { match self { ConfigValue::Map(map) => map.get(key), _ => None, } } } fn main() { let mut config = HashMap::new(); config.insert("host".to_string(), ConfigValue::String("localhost".to_string())); config.insert("port".to_string(), ConfigValue::Integer(8080)); config.insert("debug".to_string(), ConfigValue::Boolean(true)); let mut nested = HashMap::new(); nested.insert("username".to_string(), ConfigValue::String("admin".to_string())); nested.insert("password".to_string(), ConfigValue::String("secret".to_string())); config.insert("database".to_string(), ConfigValue::Map(nested)); // Access configuration if let Some(ConfigValue::String(host)) = config.get("host") { println!("Host: {}", host); } if let Some(ConfigValue::Integer(port)) = config.get("port") { println!("Port: {}", port); } // Nested access if let Some(db) = config.get("database") { if let Some(ConfigValue::String(user)) = db.get("username") { println!("DB User: {}", user); } } // Using helper methods if let Some(host) = config.get("host").and_then(|v| v.as_string()) { println!("Host (via method): {}", host); } } 9. Enums and Traits
Implementing Traits for Enums
use std::fmt; #[derive(Debug, Clone, PartialEq)] enum Animal { Dog { name: String, age: u32 }, Cat { name: String, lives: u32 }, Bird { species: String, can_fly: bool }, } // Implement Display trait impl fmt::Display for Animal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Animal::Dog { name, age } => { write!(f, "Dog {} ({} years old)", name, age) } Animal::Cat { name, lives } => { write!(f, "Cat {} ({} lives)", name, lives) } Animal::Bird { species, can_fly } => { if *can_fly { write!(f, "Flying {}", species) } else { write!(f, "Flightless {}", species) } } } } } // Implement custom trait trait Speak { fn speak(&self) -> String; } impl Speak for Animal { fn speak(&self) -> String { match self { Animal::Dog { .. } => "Woof!".to_string(), Animal::Cat { .. } => "Meow!".to_string(), Animal::Bird { can_fly, .. } => { if *can_fly { "Tweet!".to_string() } else { "Squawk!".to_string() } } } } } // Implement Default impl Default for Animal { fn default() -> Self { Animal::Dog { name: "Rex".to_string(), age: 1, } } } fn main() { let animals = vec![ Animal::Dog { name: "Buddy".to_string(), age: 5 }, Animal::Cat { name: "Whiskers".to_string(), lives: 9 }, Animal::Bird { species: "Parrot".to_string(), can_fly: true }, ]; for animal in &animals { println!("{} says: {}", animal, animal.speak()); } let default_dog = Animal::default(); println!("Default: {}", default_dog); } Trait Objects with Enums
// Trait for different types of errors trait AppError: std::fmt::Debug { fn severity(&self) -> u32; fn message(&self) -> String; } #[derive(Debug)] enum NetworkError { Timeout, ConnectionRefused, DnsFailure, } impl AppError for NetworkError { fn severity(&self) -> u32 { match self { NetworkError::Timeout => 2, NetworkError::ConnectionRefused => 4, NetworkError::DnsFailure => 3, } } fn message(&self) -> String { match self { NetworkError::Timeout => "Connection timed out".to_string(), NetworkError::ConnectionRefused => "Connection refused".to_string(), NetworkError::DnsFailure => "DNS lookup failed".to_string(), } } } #[derive(Debug)] enum DatabaseError { ConnectionFailed, QueryFailed(String), DuplicateEntry, } impl AppError for DatabaseError { fn severity(&self) -> u32 { match self { DatabaseError::ConnectionFailed => 5, DatabaseError::QueryFailed(_) => 3, DatabaseError::DuplicateEntry => 2, } } fn message(&self) -> String { match self { DatabaseError::ConnectionFailed => "Database connection failed".to_string(), DatabaseError::QueryFailed(msg) => format!("Query failed: {}", msg), DatabaseError::DuplicateEntry => "Duplicate entry".to_string(), } } } fn main() { let errors: Vec<Box<dyn AppError>> = vec![ Box::new(NetworkError::Timeout), Box::new(DatabaseError::QueryFailed("SELECT * FROM users".to_string())), Box::new(NetworkError::ConnectionRefused), Box::new(DatabaseError::ConnectionFailed), ]; for error in &errors { println!("Error: {}", error.message()); println!("Severity: {}", error.severity()); println!("Debug: {:?}", error); println!(); } // Sort errors by severity let mut errors = errors; errors.sort_by(|a, b| a.severity().cmp(&b.severity())); println!("Errors sorted by severity:"); for error in errors { println!(" {} (severity {})", error.message(), error.severity()); } } 10. Advanced Enum Patterns
State Machine with Enums
#[derive(Debug, PartialEq)] enum ConnectionState { Disconnected, Connecting { attempts: u32, timeout: u32 }, Connected { session_id: String, bytes_transferred: u64 }, Error(String), } struct Connection { state: ConnectionState, } impl Connection { fn new() -> Self { Connection { state: ConnectionState::Disconnected, } } fn connect(&mut self) { self.state = ConnectionState::Connecting { attempts: 0, timeout: 5, }; } fn disconnect(&mut self) { self.state = ConnectionState::Disconnected; } fn tick(&mut self) { use ConnectionState::*; match &mut self.state { Disconnected => { println!("Already disconnected"); } Connecting { attempts, timeout } => { if *timeout > 0 { *timeout -= 1; println!("Connecting... (attempt {}, timeout {})", attempts, timeout); } else if *attempts < 3 { *attempts += 1; *timeout = 5; println!("Retry attempt {}", attempts); } else { self.state = Error("Connection timeout".to_string()); } } Connected { session_id, bytes_transferred } => { *bytes_transferred += 1024; println!("Connected: {} ({} bytes)", session_id, bytes_transferred); } Error(msg) => { println!("Error state: {}", msg); } } } fn on_connected(&mut self, session_id: String) -> Result<(), String> { match &self.state { ConnectionState::Connecting { .. } => { self.state = ConnectionState::Connected { session_id, bytes_transferred: 0, }; Ok(()) } state => Err(format!("Cannot connect from state: {:?}", state)), } } } fn main() { let mut conn = Connection::new(); conn.connect(); for _ in 0..20 { conn.tick(); if conn.state == ConnectionState::Connected { break; } } // Simulate successful connection let _ = conn.on_connected("sess_123".to_string()); for _ in 0..5 { conn.tick(); } } Visitor Pattern with Enums
#[derive(Debug)] enum Expression { Constant(i32), Add(Box<Expression>, Box<Expression>), Subtract(Box<Expression>, Box<Expression>), Multiply(Box<Expression>, Box<Expression>), Divide(Box<Expression>, Box<Expression>), Negate(Box<Expression>), } impl Expression { fn evaluate(&self) -> Option<i32> { match self { Expression::Constant(n) => Some(*n), Expression::Add(l, r) => Some(l.evaluate()? + r.evaluate()?), Expression::Subtract(l, r) => Some(l.evaluate()? - r.evaluate()?), Expression::Multiply(l, r) => Some(l.evaluate()? * r.evaluate()?), Expression::Divide(l, r) => { let r_val = r.evaluate()?; if r_val == 0 { None } else { Some(l.evaluate()? / r_val) } } Expression::Negate(e) => Some(-e.evaluate()?), } } fn depth(&self) -> usize { match self { Expression::Constant(_) => 1, Expression::Add(l, r) | Expression::Subtract(l, r) | Expression::Multiply(l, r) | Expression::Divide(l, r) => { 1 + l.depth().max(r.depth()) } Expression::Negate(e) => 1 + e.depth(), } } fn count_ops(&self) -> usize { match self { Expression::Constant(_) => 0, Expression::Add(l, r) | Expression::Subtract(l, r) | Expression::Multiply(l, r) | Expression::Divide(l, r) => { 1 + l.count_ops() + r.count_ops() } Expression::Negate(e) => 1 + e.count_ops(), } } } fn main() { // Build expression: (3 + 4) * (5 - 2) let expr = Expression::Multiply( Box::new(Expression::Add( Box::new(Expression::Constant(3)), Box::new(Expression::Constant(4)) )), Box::new(Expression::Subtract( Box::new(Expression::Constant(5)), Box::new(Expression::Constant(2)) )) ); println!("Expression depth: {}", expr.depth()); println!("Number of operations: {}", expr.count_ops()); println!("Result: {:?}", expr.evaluate()); } 11. Performance Considerations
Memory Layout
use std::mem; #[derive(Debug)] enum SimpleEnum { A, B, C, } #[derive(Debug)] enum DataEnum { Empty, Number(i32), Text(String), Large([u8; 1024]), } fn main() { // Size of enums println!("Size of SimpleEnum: {} bytes", mem::size_of::<SimpleEnum>()); println!("Size of Option<i32>: {} bytes", mem::size_of::<Option<i32>>()); println!("Size of Option<&i32>: {} bytes", mem::size_of::<Option<&i32>>()); println!("Size of DataEnum: {} bytes", mem::size_of::<DataEnum>()); println!("Size of DataEnum::Empty: {} bytes", mem::size_of_val(&DataEnum::Empty)); println!("Size of DataEnum::Number(42): {} bytes", mem::size_of_val(&DataEnum::Number(42))); // Alignment println!("Alignment of DataEnum: {} bytes", mem::align_of::<DataEnum>()); // Using Box to reduce size for large variants #[derive(Debug)] enum OptimizedEnum { Small(i32), Large(Box<[u8; 1024]>), // Box reduces size } println!("Size of OptimizedEnum: {} bytes", mem::size_of::<OptimizedEnum>()); } Zero-Cost Abstractions
fn main() { // Enums are zero-cost abstractions let numbers = vec![1, 2, 3, 4, 5]; // Using Option - no runtime overhead fn find_even(numbers: &[i32]) -> Option<i32> { for &n in numbers { if n % 2 == 0 { return Some(n); } } None } // Using Result - no runtime overhead fn divide(a: i32, b: i32) -> Result<i32, &'static str> { if b == 0 { Err("division by zero") } else { Ok(a / b) } } // The compiler optimizes these to efficient code if let Some(even) = find_even(&numbers) { println!("Found even: {}", even); } match divide(10, 2) { Ok(result) => println!("Division result: {}", result), Err(e) => println!("Error: {}", e), } } 12. Testing and Documentation
Testing Enums
#[derive(Debug, PartialEq)] enum HttpStatus { Ok = 200, NotFound = 404, InternalServerError = 500, } impl HttpStatus { fn from_code(code: u16) -> Option<Self> { match code { 200 => Some(HttpStatus::Ok), 404 => Some(HttpStatus::NotFound), 500 => Some(HttpStatus::InternalServerError), _ => None, } } fn is_success(&self) -> bool { matches!(self, HttpStatus::Ok) } fn is_client_error(&self) -> bool { matches!(self, HttpStatus::NotFound) } fn is_server_error(&self) -> bool { matches!(self, HttpStatus::InternalServerError) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_from_code() { assert_eq!(HttpStatus::from_code(200), Some(HttpStatus::Ok)); assert_eq!(HttpStatus::from_code(404), Some(HttpStatus::NotFound)); assert_eq!(HttpStatus::from_code(500), Some(HttpStatus::InternalServerError)); assert_eq!(HttpStatus::from_code(418), None); } #[test] fn test_status_classification() { assert!(HttpStatus::Ok.is_success()); assert!(!HttpStatus::Ok.is_client_error()); assert!(!HttpStatus::Ok.is_server_error()); assert!(HttpStatus::NotFound.is_client_error()); assert!(!HttpStatus::NotFound.is_success()); assert!(!HttpStatus::NotFound.is_server_error()); assert!(HttpStatus::InternalServerError.is_server_error()); assert!(!HttpStatus::InternalServerError.is_success()); assert!(!HttpStatus::InternalServerError.is_client_error()); } #[test] fn test_discriminant_values() { assert_eq!(HttpStatus::Ok as u16, 200); assert_eq!(HttpStatus::NotFound as u16, 404); assert_eq!(HttpStatus::InternalServerError as u16, 500); } } Documentation Examples
/// Represents different types of network events /// /// # Examples /// /// ``` /// use mylib::NetworkEvent; /// /// let event = NetworkEvent::ConnectionEstablished { /// address: "192.168.1.1".to_string(), /// port: 8080, /// }; /// /// match event { /// NetworkEvent::ConnectionEstablished { address, port } => { /// println!("Connected to {}:{}", address, port); /// } /// _ => println!("Other event"), /// } /// ``` #[derive(Debug)] pub enum NetworkEvent { /// A new connection was established ConnectionEstablished { /// Remote address address: String, /// Remote port port: u16, }, /// Data was received DataReceived { /// The data that was received data: Vec<u8>, /// Number of bytes received size: usize, }, /// Connection was closed ConnectionClosed { /// Reason for closure, if any reason: Option<String>, }, /// An error occurred Error(String), } impl NetworkEvent { /// Returns a string describing the event /// /// # Examples /// /// ``` /// # use mylib::NetworkEvent; /// let event = NetworkEvent::ConnectionClosed { reason: None }; /// assert_eq!(event.description(), "Connection closed"); /// ``` pub fn description(&self) -> &str { match self { NetworkEvent::ConnectionEstablished { .. } => "Connection established", NetworkEvent::DataReceived { .. } => "Data received", NetworkEvent::ConnectionClosed { .. } => "Connection closed", NetworkEvent::Error(_) => "Error occurred", } } /// Returns true if this event indicates an error condition pub fn is_error(&self) -> bool { matches!(self, NetworkEvent::Error(_)) } } Conclusion
Enums are one of Rust's most powerful features:
Key Takeaways
- Type Safety: Enums ensure only valid states are representable
- Pattern Matching: Work seamlessly with
matchfor exhaustive handling - Data Carriers: Each variant can hold different types of data
- Option and Result: Built-in enums for common patterns
- Memory Efficiency: Only as large as the largest variant plus tag
- Expressiveness: Perfect for modeling complex domains
Common Patterns
| Pattern | Use Case | Example |
|---|---|---|
| Simple Enum | Fixed set of values | Direction { North, South, ... } |
| Enum with Data | Variants need associated data | Message::Write(String) |
| Option | Optional values | Option<T> |
| Result | Error handling | Result<T, E> |
| State Machine | Modeling states | ConnectionState |
| Visitor | Processing variants | Expression |
Best Practices
- Make impossible states unrepresentable: Use enums to encode valid states in the type system
- Use exhaustive matching: Let the compiler ensure all variants are handled
- Prefer
Optionover custom null-like types - Use
Resultfor fallible operations - Implement common traits (
Debug,Clone,PartialEq) for enums - Consider
Boxfor large variants to reduce memory footprint - Use
matchwith guards for complex conditional logic
Enums are fundamental to idiomatic Rust code and mastering them is essential for writing robust, expressive programs.