We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 19 of 24
Blanket impls, and why you can never carve out an exception
You have already used three blanket impls without writing one:
impl<T: Display + ?Sized> ToString for T { ... } // Display => .to_string()
impl<T, U: From<T>> Into<U> for T { ... } // From => .into()
impl<T, U: TryFrom<T>> TryInto<U> for T { ... } // TryFrom => .try_into()
Each is a single impl covering an unbounded number of types, conditional on a bound. This item is where you write one and then discover its permanent consequence.
Your task
pub trait Describe { fn describe(&self) -> String; }
impl<T: Debug> Describe for T { ... }
pub fn run(nums: Vec<i64>, label: String, flag: bool) -> Vec<String>
describe renders "{type_name}: {value:?}" — the fully-qualified type name
from std::any::type_name::<T>(), a colon and a space, then the Debug
rendering. run calls it on five values and returns the results in order:
alloc::vec::Vec<i64>: [1, 2, 3]
alloc::string::String: "hi"
bool: true
u8: 42
&str: "hi"
Nothing in run changes; the whole exercise is the impl block. Notice that
the last three call describe on a bool literal, a u8 literal and a
string literal — types you did not write and could not modify. That is what a
blanket impl of your own trait buys you.
Two details in that output worth reading twice. The String line shows quotes
because Debug for strings escapes and quotes; the &str line shows the type
as &str, not str, because the blanket impl carries an implicit T: Sized
bound and str is unsized — so method resolution autorefs one step further
and lands on &str.
The consequence
Once your impl works, add this:
impl Describe for String {
fn describe(&self) -> String { format!("a string: {self}") }
}
error[E0119]: conflicting implementations of trait `Describe`
for type `String`
= note: upstream crates may add a new impl of trait `Debug` ...
The starter ships that String impl, so this is the first thing you see.
Rust has no stable specialisation, so “blanket impl plus one override” is
simply not expressible. There is no default fn, no priority ordering, no
escape. If you publish a trait with a blanket impl, nobody — including you —
can ever write a more specific impl for it.
That makes a blanket impl over an unbounded T a significant API commitment,
and one of the standard mistakes in a first Rust library. Two rules of thumb:
-
Bound it as tightly as you can.
impl<T: Debug> Describe for Tat least excludes non-Debugtypes.impl<T> Describe for Twould exclude nothing and make the trait useless to everyone. -
Blanket-impl your own trait, never a foreign one.
impl<T: Debug> Describe for Tis fine here becauseDescribeis local.impl<T: Debug> Display for Twould be E0117 — the orphan rule — and evenimpl<T> MyTrait for Vec<T>can hit E0210 depending on where the parameters sit. The next problem takes those two codes apart properly.
Why the compiler is so cautious
Read that error note again: “upstream crates may add a new impl of trait
Debug“. Even if no type in your program is both a String and something
weird, the compiler reasons about what a future version of another crate could
do. Coherence is a whole-program property that has to survive recompilation
against new dependency versions, so the rules are deliberately conservative.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.