Skip to Content
Course content

193: Blanket Implementations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 Summarizable trait.
  • Write the blanket implementation for all T: std::fmt::Display.
  • Create a custom struct Article, implement Display for it, and then call .summarize() on an instance of Article to prove the blanket implementation is working.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.