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
193: Blanket Implementations
Wait, what exactly is a "blanket implementation"?
In most of the course so far, we've been implementing traits for specific types—like implementing Display for a User struct. A blanket implementation is different. Instead of targeting one type, you're targeting any type that already implements a certain trait.
Think of it as a way to say: "If a type is capable of X, it should automatically be capable of Y." A classic example in the standard library is how ToString is implemented. You don't manually implement ToString; instead, Rust has a blanket implementation that says if a type implements Display, it automatically gets ToString for free. I find this incredibly powerful for reducing boilerplate in larger projects.
// Let's say we have a trait for things that can be sent to a log file
trait Loggable {
fn log(&self);
}
// Here is the blanket implementation:
// "For any type T that implements std::fmt::Display, implement Loggable for T"
impl<T: std::fmt::Display> Loggable for T {
fn log(&self) {
println!("[LOG]: {}", self);
}
}
fn main() {
// i32 implements Display, so it automatically implements Loggable
10.log();
// String implements Display, so it also works
"System failure".log();
}
How is this different from just using a generic function?
You might be wondering why we don't just write a function like fn log_it<T: Display>(item: T). While that works for a single action, a blanket implementation lets you add a capability to a type. This means you can use that trait as a bound in other generic contexts.
If I have a Logger struct that needs to store a collection of things that can be logged, I can't just use a function. I need a trait bound. By using a blanket implementation, I've suddenly expanded the universe of types that can be passed into my Logger without having to manually write impl Loggable for i32, impl Loggable for String, and so on for every single type in my app.
Can I just implement a trait for every single type using impl<T> Trait for T?
Technically, you can, but you'll likely regret it very quickly. When you implement a trait for T (without any bounds), you are claiming that every single type in existence—including types defined in other crates and types that haven't even been written yet—implements your trait.
The problem is overlap. If you provide a blanket implementation for all T, and then later try to write a specialized implementation for a specific type like u32 to make it behave differently, the compiler will scream at you. Rust cannot allow two different implementations of the same trait for the same type. It's a "conflicting implementations" error. I usually suggest being as specific as possible with your bounds (like T: Display) to avoid locking yourself out of future specializations.
📋 Practical Task
Implementing an Automatic Summary Generator
You are building a documentation system. You have a trait called Summarizable that requires a method summarize(&self) -> String.
Instead of implementing Summarizable for every single struct in your system, your task is to create a blanket implementation. This implementation should grant Summarizable to any type that already implements std::fmt::Display. The summarize method should simply return the Display string wrapped in brackets, like this: "[Summary: the display text]".
Requirements:
- Define the
Summarizabletrait. - Write the blanket implementation for all
T: std::fmt::Display. - Create a custom struct
Article, implementDisplayfor it, and then call.summarize()on an instance ofArticleto prove the blanket implementation is working.
There are no comments for now.