Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-069 · Case file with fixtures · Case 41 of 694 · Compiler evidence

E0210: Why `From<LocalWrapper<T>> for T` Still Breaks Rust's Orphan Rule

A local type somewhere in a foreign-trait impl is not enough. Read Self and trait parameters in coherence order: no uncovered generic may appear before the first local type. Reverse the conversion or introduce a local target.

Reviewed
Rust
Rust 1.98.1
Targets
all targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
In the ordered foreign-trait implementation header, the uncovered parameter T appears as Self before the first local type covers it.
First discriminating check
Read the impl header in Self-then-trait-argument order and locate every uncovered parameter before the first local nominal type.

The short orphan-rule explanation—“either the trait or the type must be local”—is not complete for generic implementations. This impl contains a local wrapper and still fails:

struct Wrapper<T>(T);

impl<T> From<Wrapper<T>> for T {
    fn from(value: Wrapper<T>) -> T {
        value.0
    }
}

Rust 1.98.1 reports E0210 because T is uncovered before the first local type. The failing fixture preserves the exact diagnostic.

From belongs to the standard library. The target T can be any type, including one owned by another crate. The local Wrapper<T> appears as the trait argument, but it appears too late in the coherence ordering to reserve this implementation safely.

Read the header in coherence order

For a foreign trait written conceptually as:

impl<P...> ForeignTrait<T1, ..., Tn> for T0

the orphan check considers T0 first, followed by T1 through Tn. In this case:

T0 = T
T1 = Wrapper<T>

The uncovered generic T appears as the self type before the first local nominal type Wrapper<T>. That gives this crate too broad a claim over a foreign trait applied to arbitrary target types.

The Reference orphan rules define the covered-parameter condition precisely. A parameter nested inside a local nominal type is covered at that position; a bare T is not.

Reverse the conversion when the local wrapper is the target

This direction is allowed:

impl<T> From<T> for Wrapper<T> {
    fn from(value: T) -> Self {
        Self(value)
    }
}

Now the self type T0 is the local Wrapper<T>. The first local type arrives before the bare trait argument T, and the parameter inside the wrapper is covered. The repaired fixture compiles and verifies the wrapped value on Rust 1.98.1.

This repair provides conversion into the wrapper. It does not provide a generic Into<T> conversion back out, because the prohibited direction remains prohibited.

Use an inherent method for extraction

The wrapper owns its API, so extraction can be explicit:

impl<T> Wrapper<T> {
    fn into_inner(self) -> T {
        self.0
    }
}

into_inner is often clearer than a fully generic conversion. It tells readers that one layer is being removed and avoids possible ambiguity with other Into targets.

A local extraction trait is another option when generic code needs a bound, but creating a trait only to mimic one inherent method may not improve the design.

Fundamental types do not make every parameter covered

References and Box receive special treatment in parts of coherence, but nesting a parameter arbitrarily does not always give a local ownership claim. Type aliases also do not create a new nominal local type; they remain aliases for the underlying type.

When the rules feel surprising, a true newtype is more reliable than an alias:

struct LocalTarget<T>(T);

The crate owns LocalTarget, can attach invariants, and can place foreign-trait implementations at a coherence position beginning with a local type.

Why the order restriction exists

Without it, two crates could each use their own local trait argument wrapper to implement the same foreign trait for the same arbitrary foreign target. A downstream crate combining them would face overlapping implementations even though neither upstream crate knew about the other.

The ordered local-type rule assigns a predictable ownership point. It is part of Rust's ability to compile crates separately and still guarantee one applicable trait implementation.

My worksheet for E0210

I rewrite the header as a list:

[Self, trait argument 1, trait argument 2, ...]

Then I mark:

  1. The first local nominal type.
  2. Every bare generic parameter before it.
  3. Whether each earlier parameter is covered by a local type.

Changing where-clauses does not fix an uncovered ordering claim. Reordering source declarations also does nothing. I need a local trait, a local target/self type, a legal ordering, or an inherent operation.

The official E0210 explanation gives multi-parameter examples where changing type order changes legality. For From, the self type is the conversion destination. Remembering that one fact makes this common wrapper case easier: a local source argument does not grant the crate a blanket foreign-trait implementation for every possible destination T.