Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
22: Enums and Pattern Matching
Think about a vending machine. When you make a selection, the machine doesn't just get a "number"; it gets a specific type of request. Maybe you pressed the button for a "Soda," but that request carries more info: which flavor? Or maybe you hit "Snack," and that needs to know which row and column. Or, perhaps the machine is in an "Out of Order" state, where no selection is even possible.
In many languages, you'd handle this with a bunch of integers or strings and a lot of if/else blocks, hoping you didn't miss a case. In Rust, we use Enums (short for enumerations). But unlike enums in C or Java, Rust enums are "sum types," meaning they can actually hold data inside each variant. It's like a box that can be one of several different shapes, and each shape can hold different things.
Defining the Variants
Let's map that vending machine logic to actual code. I'll use a payment system example here because it's a classic scenario where you have a few distinct options, some of which need extra data.
enum PaymentMethod {
Cash, // No extra data needed
CreditCard(String), // Needs a card number
PayPal(String), // Needs an email address
Crypto { wallet: String, coin: String }, // Named fields for clarity
}
Notice how Cash is just a label, but CreditCard and PayPal are essentially tuples, and Crypto is like a mini-struct. This is the superpower of Rust enums: they let you group related options together, even if those options require completely different pieces of information.
Handling the Data with Match
Defining the enum is only half the battle. The real magic happens when you use match. I like to think of match as a strict accountant. It forces you to account for every single possibility defined in your enum. If you forget one, the compiler will yell at you before the code even runs.
fn process_payment(method: PaymentMethod) {
match method {
PaymentMethod::Cash => {
println!("Handing over the change...");
}
PaymentMethod::CreditCard(number) => {
println!("Charging card ending in {}...", &number[number.len()-4..]);
}
PaymentMethod::PayPal(email) => {
println!("Redirecting to PayPal for {}...", email);
}
PaymentMethod::Crypto { wallet, coin } => {
println!("Requesting {} from wallet {}...", coin, wallet);
}
}
}
See how we "destructure" the data? In the CreditCard arm, we aren't just checking if it's a credit card; we're extracting the number string and bringing it into the scope of that block. It's clean, it's safe, and it's impossible to accidentally try to access a "card number" when the user paid with cash.
The Shortcut for Single Cases
Now, I'll be honest: sometimes match feels like overkill. If you only care about one specific variant and want to ignore everything else, writing out every single arm is a chore. That's where if let comes in.
If you only care if the payment was via PayPal, you can do this:
if let PaymentMethod::PayPal(email) = method {
println!("Sending a confirmation email to {}!", email);
} else {
println!("Payment method doesn't support email notifications.");
}
It's essentially syntactic sugar for a match that has one specific arm and one big "catch-all" arm (the _ pattern). Use match when you need to be exhaustive; use if let when you're just hunting for one specific value.
📋 Practical Task
Building a Web API Response Processor
In real-world Rust, you'll frequently use enums to handle API responses. Your task is to build a small response processor.
Requirements:
- Create an enum called
ApiResponsewith the following variants:Success(String): Holds the response body.ClientError(u16): Holds the HTTP status code (e.g., 404).ServerError(u16): Holds the HTTP status code (e.g., 500).Timeout: No extra data.
- Write a function
handle_response(response: ApiResponse)that uses amatchstatement to print a user-friendly message for each case:- For
Success, print "Request succeeded: [body]". - For
ClientError, print "Client error occurred. Status: [code]". - For
ServerError, print "Server is having trouble. Status: [code]". - For
Timeout, print "The request timed out. Please try again."
- For
- In your
mainfunction, create a vector of differentApiResponsevariants and loop through them, passing each one to yourhandle_responsefunction.
There are no comments for now.