We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 20 of 24
The orphan rule and the newtype escape
This is the single most-complained-about restriction in Rust, and the
complaint is always the same shape: “I just want to impl Display for Vec<i32>, why won’t you let me?”
error[E0117]: only traits defined in the current crate can be implemented
for types defined outside of the crate
|
5 | impl fmt::Display for Vec<i32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| | |
| | `Vec` is not defined in the current crate
| impl doesn't use only types from inside the current crate
Why the rule exists
Trait resolution has to be coherent: for any (trait, type) pair there must be at most one impl in the whole program, and which one it is must not depend on link order or on which crates happen to be present.
Now imagine the rule did not exist. Crate pretty writes
impl Display for Vec<i32> rendering [1, 2, 3]. Crate terse writes
impl Display for Vec<i32> rendering 1 2 3. Your program depends on both.
Which one runs? There is no answer that is not arbitrary, and worse, adding an
unrelated dependency could silently change your output.
So Rust requires that for impl Trait for Type, at least one of the two must
be local to your crate. Whoever owns the trait, or whoever owns the type, is
responsible for the impl — and nobody else can conflict with them. Once you see
it as “who is allowed to make this decision”, the frustration turns into a
design principle.
Your task
pub struct Wrap(pub Vec<i32>);
impl Display for Wrap { ... }
pub fn render(rows: Vec<Vec<i32>>) -> Vec<String>
The newtype escape: wrap the foreign type in a local one, and now the
Self half of the impl is yours. Wrap is a one-field tuple struct with the
same layout as the Vec inside it — the wrapper costs nothing at runtime.
Each row renders as its Debug form:
[1, 2, 3]
[]
[-1]
Note the space after each comma; that is Debug for slices, not something you
format yourself. Delegating Display to {:?} of the inner value is
perfectly reasonable here.
E0117 vs E0210 — learners conflate these
Both are orphan-rule errors and they are not the same.
E0117 — “only traits defined in the current crate can be implemented for types defined outside of the crate”. Foreign trait, foreign type. Nothing in the impl is yours.
impl Display for Vec<i32> {} // E0117
E0210 — “type parameter T must be used as the type parameter for some
local type”. Here the Self type is local in a sense, but a type parameter
appears in an uncovered position, so the impl could still collide with a
future upstream impl.
impl<T> From<T> for MyWrapper { } // E0210: T is uncovered
The formal statement, which is worth reading once even if it does not stick:
for impl<P...> Trait<T1..Tn> for T0, either Trait is local, or at least
one Ti is a local type with no uncovered type parameters preceding it.
“Uncovered” means a bare T not wrapped in a local type constructor.
One special case you will meet in real code: Box and a few other types are
fundamental, meaning Box<LocalType> counts as local for orphan purposes.
That is why impl Trait for Box<MyType> works when
impl Trait for Vec<MyType> does not.
A framing limit of this harness
Everything you write here lives in one crate, so strictly speaking nothing you do is ever “foreign” to a different crate of yours. The rule can only be demonstrated against std types — which is exactly what this problem does. In a real multi-crate project the same rule applies at every crate boundary, and it is what makes it safe for you to depend on two libraries that have never heard of each other.
What the newtype costs
Wrapping is not free in ergonomics, only in runtime:
-
Wraphas none ofVec‘s inherent methods.w.len()does not compile unless you writew.0.len()or add delegating methods. -
You can add
Deref<Target = Vec<i32>>to get them all back — and you have just read a whole problem about why that is usually a bad idea. For a wrapper whose point is to change how the type behaves, deref-ing to the inner type undoes the point. -
The usual middle path is to implement exactly the traits you need
(
Display,IntoIterator,From<Vec<i32>>) and delegate a handful of methods by hand.
And when you write that From impl, remember clippy::from_over_into:
implement From<Vec<i32>> for Wrap, never Into<Wrap> for Vec<i32> — which
would be E0117 anyway, since both halves would be foreign.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.