Step 4 of 7
Blanket impls and extension traits
A blanket impl implements a trait for every type that meets a bound, in one go: impl<T: Display> Shout for T. Read it as "for all types T that can be displayed, here's how they shout".
use std::fmt::{self, Display};
trait Shout {
fn shout(&self) -> String;
}
impl<T: Display> Shout for T {
fn shout(&self) -> String {
format!("{}!", self.to_string().to_uppercase())
}
}
struct Crab;
impl Display for Crab {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ferris")
}
}
fn main() {
println!("{}", "hello".shout());
println!("{}", 42.shout());
println!("{}", Crab.shout());
}
HELLO!
42!
FERRIS!
Crab never mentions Shout, yet it can shout: implementing Display was enough.
You've been using blanket impls all along
impl<T: Display + ?Sized> ToString for Tin std is why every type withDisplayhas.to_string(). (?Sizedis explained at the end of this module.)impl<T, U: From<T>> Into<U> for Tis why implementingFromgives youIntofor free.impl<T: Iterator> Itertools for Tis how theitertoolscrate adds dozens of methods to every iterator in your program.
That last one is an extension trait: a trait you write to add methods to types you don't own (str, Vec, every iterator). Bring the trait into scope with use and the methods appear. Teams use them for small helpers like .is_blank() on strings or .sum_by() on iterators.
Coherence: no two impls may overlap
Once the blanket impl exists, you can't add a specific impl that it already covers:
use std::fmt::Display;
trait Shout {
fn shout(&self) -> String;
}
impl<T: Display> Shout for T {
fn shout(&self) -> String {
format!("{self}!")
}
}
impl Shout for i32 {
fn shout(&self) -> String {
format!("{self}!!!")
}
}
fn main() {
println!("{}", 5.shout());
}
error[E0119]: conflicting implementations of trait `Shout` for type `i32`
i32 is Display, so it already has Shout. Rust insists there's always exactly one impl to pick, which keeps behavior predictable. (Picking the "more specific" impl, called specialization, isn't stable Rust.)
Common mistakes
- A blanket impl in a published library is a commitment: adding one later can break users who wrote their own impls, so library authors add them early or not at all.
- Blanket-implementing a trait you don't own:
impl<T: Debug> Display for Tis rejected by the orphan rule (next step), because it would cover types from every other crate too.
Your task
Write one blanket impl so that every type that implements Debug gets a pretty() method returning its Debug text inside angle brackets: 5.pretty() is "<5>", vec![1, 2].pretty() is "<[1, 2]>".
Challenge 2
Write a blanket impl of Between for every type that implements PartialOrd. x.between(&low, &high) is true when low <= x <= high (both ends included). It should work for integers, floats, chars and strings without any extra impls.
Challenge 3
Write an extension trait from scratch. TotalLen has one method, fn total_len(self) -> usize, that adds up the byte lengths of a collection of strings. Give it a blanket impl for every type I that implements IntoIterator and whose items implement AsRef<str>, so it works on Vec<&str>, Vec<String>, arrays, and iterators like words.iter().
Previous: Supertraits and fully qualified syntax Next: Newtypes, the orphan rule and type aliases