Rust - Enums
Rust - Enums
Section titled “Rust - Enums”Enumerations, or enums, are a core feature in Rust that allow you to define a type by listing its possible variants. When you have a value that can only be one of a specific set of possibilities, an enum is the perfect tool.
enum WebEvent { // A unit-like variant PageLoad, // A tuple-struct variant KeyPress(char), // A struct-like variant Click { x: i64, y: i64 },}Here, WebEvent is a single type that can be one of three distinct variants. This approach is more concise and type-safe than defining multiple separate structs.
Defining and Instantiating Enums
Section titled “Defining and Instantiating Enums”Let’s create instances of the WebEvent variants. To make our custom type printable for debugging, we can add the #[derive(Debug)] attribute. This automatically implements the Debug trait, allowing us to use the {:?} format specifier in println!.
#[derive(Debug)]enum WebEvent { PageLoad, KeyPress(char), Click { x: i64, y: i64 },}
fn main() { let load = WebEvent::PageLoad; let key = WebEvent::KeyPress('k'); let click = WebEvent::Click { x: 20, y: 80 };
println!("{:?}", load); println!("{:?}", key); println!("{:?}", click);}Output
Section titled “Output”PageLoadKeyPress('k')Click { x: 20, y: 80 }The match Control Flow Expression
Section titled “The match Control Flow Expression”Rust has a powerful control flow expression, match, that allows you to compare a value against a series of patterns and then execute code based on which pattern matches. Enums and match are a perfect combination.
A key advantage of match is that it’s exhaustive. The compiler checks that you’ve covered every possible variant of the enum. If you forget one, your code won’t compile, preventing a whole class of bugs.
fn inspect_event(event: WebEvent) { match event { WebEvent::PageLoad => println!("Page loaded"), WebEvent::KeyPress(c) => println!("Pressed '{}'.", c), WebEvent::Click { x, y } => { println!("Clicked at x={}, y={}.", x, y); } }}
fn main() { let load = WebEvent::PageLoad; let key = WebEvent::KeyPress('k'); let click = WebEvent::Click { x: 20, y: 80 };
inspect_event(load); inspect_event(key); inspect_event(click);}Output
Section titled “Output”Page loadedPressed 'k'.Clicked at x=20, y=80.The Option Enum: Handling Absence
Section titled “The Option Enum: Handling Absence”Rust doesn’t have null. Instead, it has a standard library enum, Option<T>, that encodes the concept of a value being present or absent. This is a huge benefit, as it forces you to handle the ‘null’ case at compile time, preventing a wide class of bugs.
enum Option<T> { Some(T), // Represents the presence of a value of type T None, // Represents the absence of a value}Let’s see it in action with a function that might not return a value. Because Option<T> is an enum, the compiler ensures you handle the None case, making your code more robust.
fn find_user(name: &str) -> Option<u32> { match name { "Alice" => Some(101), "Bob" => Some(102), _ => None, }}
fn main() { let user_id = find_user("Alice"); match user_id { Some(id) => println!("Found user ID: {}", id), None => println!("User not found."), }}Output
Section titled “Output”Found user ID: 101The if let and while let Syntax
Section titled “The if let and while let Syntax”Sometimes, a match expression is too verbose if you only care about one specific variant. For these cases, Rust provides if let and while let as more concise syntax.
fn main() { let maybe_number: Option<i32> = Some(7);
// Using `if let` is more concise than a `match` with a single arm. if let Some(number) = maybe_number { println!("The number is: {}", number); } else { println!("There is no number."); }
// `while let` loops as long as the pattern matches. let mut optional = Some(0); while let Some(i) = optional { if i > 5 { println!("Greater than 5, quitting."); optional = None; } else { println!("`i` is `{:?}`. Try again.", i); optional = Some(i + 1); } }}if let Some(number) = maybe_number can be read as: “if maybe_number is a Some value, bind its inner value to the number variable for the if block.”
Best Practices and Common Errors
Section titled “Best Practices and Common Errors”- Embrace
matchandif let: Always use these constructs to handleOptionand other enums. Avoid.unwrap()or.expect(), which can cause your program to panic (crash). Use.expect("message")only when you are absolutely certain a value will beSomeand aNonerepresents a critical, unrecoverable bug. - Use Enums to Model State: Enums are excellent for creating state machines. For example,
enum PageState { Loading, Loaded(Content), Error(String) }. This makes invalid states unrepresentable in your code. - Keep Variants Cohesive: The variants of an enum should represent different states of a single, unified concept.
Further Learning: The Result Enum
Section titled “Further Learning: The Result Enum”Similar to Option, the Result<T, E> enum is the standard way to handle operations that can succeed or fail. It is defined as enum Result<T, E> { Ok(T), Err(E) }. You’ll encounter it everywhere in Rust, especially in I/O and parsing operations. We’ll cover it in more detail in the error handling chapter.