WEP: Super Traits
Context
A trait cannot state that implementing it requires implementing another. Every prerequisite is therefore unwritten, and since Wado monomorphizes everything, each one surfaces as an error inside the library rather than at the impl that is actually wrong:
trait Circle {
fn radius(&self) -> i32;
fn describe(&self) -> String { return `r=${self.radius()} a=${self.area()}`; }
}
// A type implementing only `Circle`:
// error: no method 'area' found on type 'Blob' ← reported at Circle::describe
The same gap makes bounds redundant — T: Ord does not imply T: Eq, so
impl<T: Eq + Ord> Eq for RangeExclusive<T> spells both — and lets Ord and
Eq be implemented independently, so a type can have a working < and a
missing ==.
Decision
Syntax
Rust-compatible:
trait Ord: Eq {
fn cmp(&self, other: &Self) -> Ordering;
}
trait Circle: Shape + Display {
fn radius(&self) -> i32;
}
The clause sits between the trait's generic parameters and its block, and takes
the same bound list generic parameters do, so + lists and associated-type
constraints (trait A: B<Item = i32>) are included.
There is no where form. It would add no expressive power: trait C where Self: S is Rust's own synonym for trait C: S, and every other where case is
either already spelled inline in Wado (bounds on generic parameters, on a
trait's own associated type, on a supertrait's) or concerns a feature Wado does
not have (dyn, lifetimes).
A fn(...) / fn mut(...) bound is rejected here: a callable signature is not a
trait a type can be required to implement.
Obligation
impl Sub for T requires T: Super for every trait in Sub's closure, not
only its direct supertraits — one satisfied structurally has no impl block of
its own to carry the rest of the chain. Reported at the impl block with a reason
chain. Structural on-demand derivation satisfies the obligation, so a plain
struct gets Ord: Eq without an impl Eq being written.
Elaboration
A declared bound T: Sub expands to the transitive closure of Sub and its
supertraits, feeding bound checking, on-demand derivation, and method lookup
alike. The derivation half is what makes T: Ord alone sufficient for ==.
The expansion happens where a bound is read, not where it is registered: a type
parameter's bounds stay as written, and the question "does T implement Eq?"
is what walks the closure. Registration sites are too many to keep in step.
An associated-type constraint written in supertrait position
(trait Sink: Collect<Item = i32>) is checked: a type binding Item = String
is rejected wherever T: Sink is required, the same as if the constraint had
been written on the bound directly. It is not yet used for inference — T: Sink
leaves T::Item unresolved rather than equal to i32, so a body must still
name the type. Implying it, as Rust ≥ 1.72 does, is follow-up work.
Cycles
A trait that reaches itself through supertraits is an error at its declaration.
Name collisions
A method reachable through more than one of a receiver's bounds is an error
where it is called, not where the traits are declared — Rust's E0034. One rule
covers all three shapes: a subtrait shadowing a supertrait method, two
supertraits of a diamond sharing one, and a <T: Left + Right> written by hand.
Rejecting at the declaration would cover only the first, and leave the other two
resolving silently to whichever bound came first.
The escape is to name the trait: Base::name(&x), the trait-qualified call
form from
WEP: Overload Resolution.
Standard library
trait Ord: Eq only. Every stdlib impl Ord already has a matching Eq, so the
obligation holds on arrival, and the redundant Eq in impl<T: Eq + Ord> comes
out.
The format-trait pairs (InspectAlt: Inspect, DisplayAlt: Display, and the
rest of the *Alt family), a shared face over the Reflect* traits, and the
stdlib Fn: FnMut that
Closure Implementation Internals
leaves open are follow-ups.
Consequences
Removing a supertrait from a published trait breaks downstream code that relied on the implied bound; adding one breaks implementors. This is the trade Rust makes, and the reason a non-implied bound carries neither risk.
Iterator becomes decomposable — ExactSizeIterator / DoubleEndedIterator
style splits are expressible for the first time — though nothing is split here.
Front-end only: no NIR, WIR, codegen, runtime, or code-size effect.
