Implementing one trait for "anything that..."
You write a trait. You'd love every type that already implements Display to also implement your new LogPretty trait, with no extra ceremony. You don't want to write impl LogPretty for u32, then impl LogPretty for String, then 50 more. You want one rule that covers everything.
Rust lets you do exactly that. The mechanism is called a blanket implementation: an impl block where the type is itself a generic, constrained by some other trait. One impl, infinite types covered, all checked at compile time.
This is one of the most powerful pieces of Rust's trait system. It's how Display magically gives you ToString for free. It's how Iterator extends to IntoIterator for any reference. Once you spot the pattern, you'll see it everywhere in the standard library.
The simplest possible example
// A trait that wants to print a value with a label.
trait LogPretty {
fn log_pretty(&self, label: &str);
}
// The blanket impl. This says: "for every type T that implements
// std::fmt::Display, implement LogPretty as well."
// We don't enumerate concrete types. We constrain by another trait.
impl<T: std::fmt::Display> LogPretty for T {
fn log_pretty(&self, label: &str) {
// Because we know T: Display, we can use {self} in a format string.
println!("[{label}] {self}");
}
}
fn main() {
// u32 implements Display, so it gets LogPretty for free.
42u32.log_pretty("number");
// String also implements Display. Same deal.
String::from("hello").log_pretty("greeting");
}
The blanket impl reads almost like English: "for any type T that displays, here's how it logs." You wrote one block. The compiler turned it into thousands of concrete impls behind the scenes, one per T you actually use.
How the standard library uses this
The classic example is ToString. Anything that's Display automatically gets a to_string() method, because the standard library has roughly this in core:
// Approximate, from core::string. The real version is slightly more careful
// about edge cases, but the shape is exactly this.
impl<T: fmt::Display + ?Sized> ToString for T {
fn to_string(&self) -> String {
// Use the Display impl to format into a fresh String.
let mut buf = String::new();
let _ = write!(buf, "{}", self);
buf
}
}
That's why 42.to_string() works. The integer doesn't directly impl ToString; the blanket impl above kicks in via Display.
Another favourite: Into from From.
// Whenever U implements From<T>, T automatically gets Into<U>.
impl<T, U: From<T>> Into<U> for T {
fn into(self) -> U {
U::from(self)
}
}
You write impl From<MyError> for AppError, and now MyError knows how to .into() an AppError without any extra code. That's a single blanket impl in core doing the work for every conversion in the entire ecosystem.
The orphan rule, and why it stings
There's a rule in Rust called the orphan rule: an impl Foo for Bar is only legal if Foo or Bar is defined in your crate. Without it, two unrelated crates could both write impl Display for u32 and the compiler would have no way to choose between them. So Rust forbids the case where both the trait and the type belong to someone else.
Blanket impls bump into this rule a lot. If you write:
// Trait you defined. Generic over T.
trait LogPretty { fn log_pretty(&self, label: &str); }
// Blanket impl over a foreign trait, for every T.
impl<T: serde::Serialize> LogPretty for T {
fn log_pretty(&self, _label: &str) { /* ... */ }
}
That's fine, because LogPretty is local to your crate. The "for T" side is generic, not foreign. So the orphan rule lets it through.
But if you tried this:
// Trying to add a blanket impl of someone else's trait, for every T.
// Both sides are foreign in spirit. The compiler refuses.
impl<T: MyTrait> std::fmt::Display for T {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.my_pretty())
}
}
You'll see something like:
error[E0210]: type parameter `T` must be used as the type parameter for some local type
|
| impl<T: MyTrait> std::fmt::Display for T {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `T` must be used as the type parameter for some local type
The fix is the newtype pattern: wrap T in a struct of yours and impl on the wrapper. That sidesteps the orphan rule because your wrapper is local.
Coherence: why you can't have two blanket impls
The flip side of "blanket impls cover infinite types" is that they can clash with each other. Suppose you write:
trait MyTrait { fn say(&self); }
// First blanket impl: every Display gets MyTrait.
impl<T: std::fmt::Display> MyTrait for T {
fn say(&self) { println!("display: {self}"); }
}
// Second blanket impl: every Debug gets MyTrait.
impl<T: std::fmt::Debug> MyTrait for T {
fn say(&self) { println!("debug: {self:?}"); }
}
The compiler refuses this with E0119: conflicting implementations of trait MyTrait. Why? Because some types implement both Display and Debug. For those, both blanket impls would apply, and the compiler couldn't pick one. This rule is called coherence, and it's what guarantees that "calling .say() on a String" has exactly one meaning, no matter which crates you depend on.
There are workarounds. Specialization is a long-running RFC for "if you have two impls and one is more specific, prefer it." It's not stable in 2025. Until it lands, you typically pick one bound or use a marker trait to disambiguate.
A more realistic example: a Validate trait
You're writing a web service. Every request type should know how to validate itself. Most validations boil down to "delegate to a serde-derived schema validator." So you write:
use serde::de::DeserializeOwned;
// A trait that says "I know how to check my fields."
pub trait Validate {
fn validate(&self) -> Result<(), String>;
}
// Default impl for anything that's DeserializeOwned + has the
// schemars::JsonSchema trait. Both common in API code.
impl<T: DeserializeOwned + schemars::JsonSchema> Validate for T {
fn validate(&self) -> Result<(), String> {
// Pretend this calls a real validator. The point is: every
// request type that's already serde+schemars gets validation
// for free.
Ok(())
}
}
Now any new request DTO you write picks up a sensible default validate(). If a particular type needs custom rules, you'd usually write a separate trait (perhaps ValidateExt) rather than try to override the blanket impl, because of the coherence issue above.
When to reach for a blanket impl
A blanket impl is the right tool when:
- The behaviour you want to add is uniform: it should be derived mechanically from one or two other traits.
- You don't expect users to customise it for specific types.
- The trait you're impl'ing is yours.
It's the wrong tool when:
- You need different behaviour per type. Use a normal trait with concrete impls instead.
- You're trying to impl a foreign trait for a foreign generic. Use the newtype pattern.
- The bound is so loose that it would conflict with reasonable future impls. Tighten the bound or split the trait.