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