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.