Blanket Trait Implementations

When a trait is local, we can implement it for as many types as we like. How far can we take this?

#![allow(unused)]
fn main() {
pub trait PrettyPrint {
    fn pretty_print(&self);
}

// A blanket implementation! If something implements Display, it implements
// PrettyPrint.
impl<T> PrettyPrint for T
where
    T: std::fmt::Display,
{
    fn pretty_print(&self) {
        println!("{self}")
    }
}
}
  • The subject of a trait implementation at the definition site of a trait can be anything, including T with no bounds.

    We can’t do anything with a T we don’t know nothing about, so this is uncommon.

  • Conditional blanket implementations are much more useful and you are more likely to see and author them.

    These implementations will have a bound on the trait, like impl <T: Display> ToString for T {...}

    In the example above we have a blanket implementation for all types that implement Display, the implementation has one piece of information available to it from the trait bounds: it implements Display::fmt.

    This is enough to write an implementation for pretty printing to console.

  • Do be careful with these kinds of implementations, as it may end up preventing users downstream from implementing a more meaningful.

    The above isn’t written for Debug as that would mean almost all types end up implementing PrettyPrint, and Debug is not semantically similar to Display: It’s meant for debug output instead of something more human-readable.

ref:

  • https://doc.rust-lang.org/reference/glossary.html#blanket-implementation