Rust Enums & Pattern Matching
Basic Enums
C-style enums and data-carrying variants
// C-style — no data
enum Direction {
North,
South,
East,
West,
}
// Data-carrying variants — each can hold different types
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
Rust enums are algebraic data types — each variant can carry different data. This makes them vastly more powerful than C/Go enums.
Pattern Matching with match
match is exhaustive — every variant must be handled
fn describe(addr: &IpAddr) -> String {
match addr {
IpAddr::V4(a, b, c, d) => format!("{a}.{b}.{c}.{d}"),
IpAddr::V6(s) => s.clone(),
}
}
// Catch-all with _ or variable
fn priority(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
500 => "Server Error",
_ => "Unknown",
}
}
match is exhaustive — the compiler forces you to handle every variant. _ is the catch-all. Forgetting a variant is a compile error, not a runtime bug.
Option<T>
Rust’s replacement for null
fn find_host(name: &str) -> Option<String> {
match name {
"sw01" => Some(String::from("10.50.1.10")),
"fw01" => Some(String::from("10.50.1.1")),
_ => None,
}
}
// Handling Option
match find_host("sw01") {
Some(ip) => println!("Found: {ip}"),
None => println!("Not found"),
}
// Combinators — avoid nested match
let ip = find_host("sw01").unwrap_or("0.0.0.0".into());
let upper = find_host("sw01").map(|ip| ip.to_uppercase());
let ip = find_host("sw01").unwrap_or_else(|| default_host());
// if let — when you only care about Some
if let Some(ip) = find_host("sw01") {
println!("Found: {ip}");
}
Option<T> replaces null. Some(value) means present, None means absent. The compiler forces you to handle both cases.
Result<T, E>
Success or error — no exceptions
use std::fs;
use std::io;
fn read_config(path: &str) -> Result<String, io::Error> {
fs::read_to_string(path)
}
match read_config("/etc/app.conf") {
Ok(contents) => println!("{contents}"),
Err(e) => eprintln!("Error: {e}"),
}
Result<T, E> is how Rust handles errors. Ok(value) for success, Err(error) for failure. No exceptions, no hidden control flow.
Enum Methods
impl blocks on enums
enum Command {
Ping { host: String },
Scan { host: String, ports: Vec<u16> },
Quit,
}
impl Command {
fn execute(&self) {
match self {
Command::Ping { host } => {
println!("pinging {host}...");
}
Command::Scan { host, ports } => {
println!("scanning {host} on {:?}", ports);
}
Command::Quit => {
println!("exiting");
}
}
}
}
Guards and Bindings
Advanced pattern matching
let num = 42;
match num {
n if n < 0 => println!("negative"),
0 => println!("zero"),
1..=10 => println!("small"),
n @ 11..=100 => println!("medium: {n}"),
_ => println!("large"),
}
// Destructuring nested enums
enum Packet {
Data { payload: Vec<u8>, port: u16 },
Ack(u32),
Error(String),
}
match packet {
Packet::Data { port: 22, .. } => println!("SSH data"),
Packet::Data { port, payload } => println!("port {port}: {} bytes", payload.len()),
Packet::Ack(seq) => println!("ack {seq}"),
Packet::Error(msg) => eprintln!("error: {msg}"),
}
Match guards (if condition) add runtime checks. @ binds a matched value to a name. .. ignores remaining fields.