Advanced Traits

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.