Rust by Example: Enums and Pattern Matching
Explore the power of Rust's algebraic data types. This example demonstrates how to define Enums that can hold different types of data and how to use the 'match' operator for exhaustive pattern matching.
Code
// Define an Enum
enum IpAddrKind {
V4,
V6,
}
// Enums can hold data directly!
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// Method body would go here
println!("Message called!");
}
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
let msg = Message::Write(String::from("hello"));
msg.call();
// --- Pattern Matching with 'match' ---
let m = Message::Move { x: 10, y: 20 };
match m {
Message::Quit => {
println!("The Quit variant");
},
Message::Move { x, y } => {
println!("Move to x: {}, y: {}", x, y);
},
Message::Write(text) => {
println!("Text message: {}", text);
},
Message::ChangeColor(r, g, b) => {
println!("Color: {}, {}, {}", r, g, b);
},
}
// Matching with Option<T>
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("Six: {:?}", six);
println!("None: {:?}", none);
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}Explanation
Enums (enumerations) allow you to define a type by enumerating its possible variants. Unlike enums in C or Java which are mostly just fancy integers, Rust enums are fully algebraic data types. This means each variant can optionally hold data, and different variants can hold different types of data. One variant might hold a String, while another holds a tuple of three integers.
Under the hood, Rust enums are typically stored as a "tagged union": a discriminant (tag) to indicate which variant it is, plus enough memory to hold the largest variant. However, Rust performs Niche Optimization. For example, in Option<&T>, the None variant is represented by a null pointer (0). Since references &T can never be null, Rust can distinguish Some(&T) from None without needing extra memory for a tag, making Option<&T> the same size as a raw pointer.
The power of enums is unlocked with the match control flow operator. match allows you to compare a value against a series of patterns and execute code based on which pattern matches. Crucially, match is exhaustive: you must handle every possible case. The compiler will not let you compile your code if you forget to handle a variant, ensuring you never have unhandled states.
Code Breakdown
enum Message shows the flexibility of Rust enums. Quit has no data, Move has named fields like a struct, Write includes a single String, and ChangeColor includes a tuple of three integers.match block. We pattern match against m. Notice how we can destructure the data inside the variants directly in the match arms, extracting x and y from Message::Move.fn plus_one(x: Option) . This function takes an Option. Because x could be None, we cannot simply add 1 to it. We MUST use match (or similar methods) to unwrap the value safely.
