We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 16 of 24
Associated type or generic parameter?
This is the most consequential API-design decision in Rust traits, and the one most often got wrong. The two shapes look almost identical:
pub trait Parser { type Out; fn parse_one(&self, raw: i64) -> Self::Out; }
pub trait Convert<T> { fn convert(&self, raw: i64) -> T; }
The difference is one sentence:
An associated type is an output determined by the impl — a type may implement the trait once. A generic parameter is an input chosen by the caller — a type may implement the trait many times.
That is why Iterator::Item is an associated type (a Vec<i32>‘s iterator
yields i32 and nothing else, ever) while From<T> is generic (a String
can be built from a &str, from a char, from a Box<str> — many impls, one
type).
Get this wrong and you either lock users out of a legitimate second impl, or you force every single call site to carry a type annotation forever.
Your task
pub struct Machine;
impl Parser for Machine { type Out = String; ... }
impl Convert<u8> for Machine { ... }
impl Convert<u16> for Machine { ... }
pub fn run(vals: Vec<i64>) -> Vec<String>
For each input value produce "<v>|byte|word", where
-
<v>comes fromparse_one—format!("<{raw}>") -
byteisraw.rem_euclid(256)as au8 -
wordisraw.rem_euclid(65_536)as au16
rem_euclid rather than % so negatives wrap the way you would expect:
-1 becomes 255 and 65535, not -1.
The error you must hit
The starter contains two impl Parser for Machine blocks, one with
type Out = String and one with type Out = u8. Compile it:
error[E0119]: conflicting implementations of trait `Parser` for type `Machine`
This is the rule made concrete. The associated type is not part of the trait’s
identity — Parser is Parser, whatever Out you pick — so two impls
collide the same way two impl Display for Money blocks would. Delete the
second one.
Then look at what Convert does instead. Convert<u8> and Convert<u16> are
different traits as far as coherence is concerned, so both impls are legal
and both coexist happily on Machine.
The cost of choosing generic
Nothing is free. Because Machine implements Convert twice, this does not
compile:
let x = m.convert(v); // which one?
You must disambiguate, either by annotating the binding or with a turbofish:
let byte: u8 = m.convert(v);
let byte = Convert::<u8>::convert(&m, v);
The starter’s run already annotates both bindings, and that is exactly the
tax a generic parameter imposes on every caller forever. Compare
parse_one, which needs no annotation at all because the impl already decided.
That trade-off is the decision. Ask: could a sensible user want two different answers here? If yes, generic parameter and accept the annotations. If no, associated type and give callers inference.
Three more errors in the same family
-
E0191:
Box<dyn Parser>is rejected — “the value of the associated typeOutmust be specified”. Associated types must be named in a trait object:Box<dyn Parser<Out = String>>. Generic parameters must be supplied:Box<dyn Convert<u8>>. Different syntax, different error, same root cause — a trait object needs one concrete shape. -
E0207: try moving the parameter onto the impl instead of the trait —
impl<T> Parser for Machine— and you get “the type parameterTis not constrained by the impl trait, self type, or predicates”. Nothing determinesT, so there would be infinitely many impls. -
E0220 is what you get for naming an associated type that does not exist,
e.g.
dyn Parser<Output = String>.
Where you have already seen both
-
Iterator::Item,Deref::Target,Add::Output,TryFrom::Error— associated types. One answer per implementing type. -
From<T>,PartialEq<Rhs>,Index<Idx>,Convert<T>here — generic parameters.String: PartialEq<str>andString: PartialEq<&str>are both real, which is exactly why that one is not associated.
Add<Rhs = Self> shows the hybrid: a generic parameter with a default, so the
common case reads like there is no parameter at all.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.