Blanket Implementations
A blanket implementation implements a trait for all types T (or all wrapper types W<T>) that satisfy a specified set of trait bounds (impl<T: Bound> MyTrait for T). In the Rust standard library and ecosystem, blanket implementations eliminate boilerplate by automatically deriving higher-level capabilities whenever a lower-level trait is implemented.
Categories of Blanket Implementations
| Category | Signature Shape | Standard Library Example |
|---|---|---|
| Universal conditional blanket impl | impl<T: Bound> Trait for T |
impl<T: Display + ?Sized> ToString for T |
| Reflexive / transitive conversion impl | impl<T, U> Into<U> for T where U: From<T> |
Implementing From<A> for B automatically provides Into<B> for A and TryFrom / TryInto |
| Pointer / reference forwarding impl | impl<T: Trait + ?Sized> Trait for &T |
impl<I: Iterator + ?Sized> Iterator for &mut I |
| Container lifting impl | impl<T: Trait> Trait for Vec<T> |
impl<T: Clone> Clone for Vec<T> |
Implementing ONE Trait (`From<UserId> for u64`) Triggers a Chain of Blanket Impls:
impl From<UserId> for u64
|
+---> Blanket Impl 1: `impl<T, U: From<T>> Into<U> for T`
| (Unlocks `user_id.into()` -> `u64`)
|
+---> Blanket Impl 2: `impl<T, U: Into<T>> TryFrom<U> for T`
| (Unlocks `u64::try_from(user_id)` -> `Result<u64, Infallible>`)
|
+---> Blanket Impl 3: `impl<T, U: TryFrom<T>> TryInto<U> for T`
(Unlocks `user_id.try_into()` -> `Result<u64, Infallible>`)
Coherence Consequences of Adding a Universal Blanket Implementation
Once a trait has a universal blanket implementation impl<T: Bound> MyTrait for T, no other crate (and no other impl block in the same crate) can ever implement MyTrait directly for any bare type U or generic parameter, because the compiler cannot prove that U will never implement Bound:
trait JsonValue {
fn to_json(&self) -> String;
}
trait PrimitiveNumber {
fn format_number(&self) -> String;
}
impl PrimitiveNumber for i64 {
fn format_number(&self) -> String {
self.to_string()
}
}
impl PrimitiveNumber for f64 {
fn format_number(&self) -> String {
format!("{:.2}", self)
}
}
// Blanket implementation of `JsonValue` for all `PrimitiveNumber` types:
impl<T: PrimitiveNumber> JsonValue for T {
fn to_json(&self) -> String {
self.format_number()
}
}
// Because `Vec<T>` is a concrete type constructor, we can also provide a container
// blanket implementation ONLY if `Vec<T>` can never implement `PrimitiveNumber`
// (when `PrimitiveNumber` is local to this crate, `rustc` knows `Vec<T>: !PrimitiveNumber`!):
impl<T: JsonValue> JsonValue for Vec<T> {
fn to_json(&self) -> String {
let items: Vec<String> = self.iter().map(JsonValue::to_json).collect();
format!("[{}]", items.join(", "))
}
}
fn main() {
let numbers = vec![10_i64, 20, 30];
println!("i64 json: {}", 42_i64.to_json());
println!("f64 json: {}", 3.5_f64.to_json());
println!("Vec<i64> json: {}", numbers.to_json());
}
i64 json: 42
f64 json: 3.50
Vec<i64> json: [10, 20, 30]
Notice why impl<T: JsonValue> JsonValue for Vec<T> compiled alongside impl<T: PrimitiveNumber> JsonValue for T: because PrimitiveNumber is a local trait defined in our own crate and we did not implement PrimitiveNumber for Vec<T>, the coherence checker knows for certain that Vec<T> does not implement PrimitiveNumber! Had PrimitiveNumber been a foreign trait (like std::fmt::Display), the second impl for Vec<T> would have been rejected with E0119 because upstream std could theoretically add impl<T> Display for Vec<T> in the future.