Complete Guide to Rust Enums

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 match expressions
  • 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

  1. Type Safety: Enums ensure only valid states are representable
  2. Pattern Matching: Work seamlessly with match for exhaustive handling
  3. Data Carriers: Each variant can hold different types of data
  4. Option and Result: Built-in enums for common patterns
  5. Memory Efficiency: Only as large as the largest variant plus tag
  6. Expressiveness: Perfect for modeling complex domains

Common Patterns

PatternUse CaseExample
Simple EnumFixed set of valuesDirection { North, South, ... }
Enum with DataVariants need associated dataMessage::Write(String)
OptionOptional valuesOption<T>
ResultError handlingResult<T, E>
State MachineModeling statesConnectionState
VisitorProcessing variantsExpression

Best Practices

  1. Make impossible states unrepresentable: Use enums to encode valid states in the type system
  2. Use exhaustive matching: Let the compiler ensure all variants are handled
  3. Prefer Option over custom null-like types
  4. Use Result for fallible operations
  5. Implement common traits (Debug, Clone, PartialEq) for enums
  6. Consider Box for large variants to reduce memory footprint
  7. Use match with guards for complex conditional logic

Enums are fundamental to idiomatic Rust code and mastering them is essential for writing robust, expressive programs.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper