Skip to content

← Generics and Traits step 12 of 24

Medium Primitives

Deref and the coercion you have been using all along

You have been relying on Deref since your first week and have never written one. It is the reason Vec<T> appears to have every [T] method, the reason String appears to have every str method, and the reason fn f(s: &str) accepts &my_string.

The trait is small:

impl Deref for Username {
    type Target = str;
    fn deref(&self) -> &str { &self.0 }
}

and the machinery it unlocks is called deref coercion. Two rules cover almost everything:

  1. Method calls. When you write u.len() and Username has no len, the compiler follows the deref chain — Usernamestr — and looks again.
  2. Reference arguments. When a function wants &str and you pass &Username, the compiler inserts the deref for you.

And one rule that surprises people: coercion does not apply to generic type-parameter matching. fn g<T: Trait>(t: &T) will not silently deref your wrapper to find an impl on the target. Coercion happens at coercion sites — method receivers and typed argument positions — not during trait selection. This is a genuine limit and worth knowing before it bites you.

Your task

pub struct Username(pub String);
impl Deref for Username { type Target = str; ... }

pub fn stats(names: Vec<String>) -> Vec<usize>

For each name, wrap it in a Username and return

u.len() + u.chars().count() + shout_len(&u)

where shout_len(s: &str) -> usize is given. That line is the whole exercise: two method calls resolved through the chain, and one argument coerced. The starter contains it already and refuses to compile — E0599 twice, E0308 once. One impl block fixes all three.

Note Target = str, not Target = String. Deref goes to the borrowed form; String itself derefs to str, Vec<T> to [T], Box<T> to T. Following that convention is what makes &username usable as a &str directly rather than after two hops.

Two of the tests turn on the difference between bytes and characters. "héllo" has 5 characters and 6 bytes. "ß" is 1 character, 2 bytes, and uppercases to "SS" — 2 bytes. Unicode case conversion can change length.

The lint that catches the over-correction

Once learners see the mechanism, they start writing it out by hand. Clippy stops them, at default-on level:

error: deref which would be done by auto-deref [clippy::explicit_auto_deref]

shout_len(&*u) is rejected because shout_len(&u) already works. The same family covers clippy::deref_addrof (*&x), clippy::borrow_deref_ref (&*some_ref) and clippy::option_as_ref_deref. Trust the coercion; write the short form.

The rule about when to implement it

The standard library’s guidance is unusually blunt: implement Deref only for smart pointers. Box, Rc, Arc, MutexGuard, Ref — types whose entire purpose is to be the thing they point at, with some ownership or synchronisation story attached.

Three conditions come with it. Deref must be

  • infallible — it returns &Target, so there is nowhere to report failure;
  • cheap — it runs implicitly and often, sometimes several times per expression;
  • non-colliding — if your type has an inherent method with the same name as one on the target, your method silently wins at every call site, and readers have no way to see it.

Username here is borderline by that standard: it is a newtype, not a smart pointer. It is a perfectly common pattern in real crates, and the next item in this track shows you exactly how it goes wrong.

Remember the grade is compile + tests + clippy -D warnings.