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
24: Implementing Traits on Structs and Enums
A few years ago, I was reviewing a PR for a teammate who was building a telemetry system. He had created three different structs—SystemLog, NetworkLog, and ApplicationLog—and for every single one, he'd written a nearly identical method called to_summary_string(). When he wanted to print a list of all logs regardless of their type, he ended up writing a massive, nested match statement that was frankly a nightmare to maintain. He asked me why he couldn't just call a single method on a collection of different log types. The answer was simple: he had the data, but he hadn't defined the behavior as a trait.
Giving Your Structs New Abilities
When you define a trait, you're essentially creating a contract. You're saying, "Any type that implements this trait promises to provide these specific functions." Implementing a trait on a struct is where the magic happens because it allows your code to stop caring about what a type is and start caring about what it can do.
Let's look at a more concrete example. Suppose we're building a notification system. We have different ways to send alerts, and we want a unified way to "dispatch" them.
trait Dispatchable {
fn dispatch(&self);
}
struct EmailNotification {
email_address: String,
subject: String,
body: String,
}
struct SMSNotification {
phone_number: String,
message: String,
}
impl Dispatchable for EmailNotification {
fn dispatch(&self) {
println!("Sending email to {}: {}", self.email_address, self.subject);
}
}
impl Dispatchable for SMSNotification {
fn dispatch(&self) {
println!("Sending SMS to {}: {}", self.phone_number, self.message);
}
}
By implementing Dispatchable for both, we've decoupled the logic. Now, I can write a function that takes any type that implements Dispatchable, and it doesn't matter if it's an email, a text, or some future Slack integration we haven't even thought of yet. I've personally found that this is the single best way to avoid "spaghetti code" as a project grows.
Handling Variants with Trait Implementations
Now, you might wonder if this works for enums. It does, and it's actually incredibly powerful. When you implement a trait on an enum, you typically use a match statement inside the trait method to define how each variant should behave. This is where Rust really shines compared to traditional OOP inheritance.
Let's say our notification system has different priority levels, and we want to format a priority label for a dashboard. Instead of a standalone function, we'll use a trait.
trait Labelable {
fn get_label(&self) -> String;
}
enum Priority {
Low,
Medium,
High,
Critical,
}
impl Labelable for Priority {
fn get_label(&self) -> String {
match self {
Priority::Low => "🟢 Low Priority".to_string(),
Priority::Medium => "🟡 Medium Priority".to_string(),
Priority::High => "🟠 High Priority".to_string(),
Priority::Critical => "🔴 CRITICAL".to_string(),
}
}
}
Notice that the impl block is for the Priority enum as a whole, not for individual variants. In Rust, variants aren't separate types; they are values of the enum type. This means the trait implementation acts as a centralized dispatcher for all those variants. It keeps your logic grouped together, which makes it much easier to track down bugs when a specific variant starts behaving weirdly.
📋 Practical Task
Exercise: Implementing a Unified Payment Processor
You are building a checkout system that needs to handle multiple payment methods. Your goal is to ensure that regardless of the payment method, the system can call a single method to process the transaction.
Requirements:
- Define a trait called
PaymentProcessorwith a methodprocess_payment(&self, amount: f64) -> bool. - Create a struct
CreditCardwith fields forcard_number(String) andexpiry_date(String). - Create a struct
CryptoWalletwith a field forwallet_address(String). - Implement the
PaymentProcessortrait for both structs.- The
CreditCardimplementation should print "Processing credit card [number] for $[amount]" and returntrue. - The
CryptoWalletimplementation should print "Processing crypto payment from [address] for $[amount]" and returntrue.
- The
- Create an enum called
PaymentStatuswith variantsPending,Completed, andFailed. - Implement a trait called
StatusDisplayfor thePaymentStatusenum that returns a human-readable string (e.g., "Payment is currently Pending"). - In your
mainfunction, create an instance of aCreditCardand aCryptoWallet, and callprocess_paymenton both.
There are no comments for now.