Status: Superseded by ADR-295 — its D1-D4 are D6-D9 there
Date: September 14, 2026
Target Version: Nikaia 0.0.8
Related: ADR-074 §4 (which named this as the prerequisite for a
bound and left it open), ADR-031 (a signature binds from the
receiver — a bound asks the same question of a different thing),
ADR-055 D1 (async fn comes off the ledger’s sync column, which
is what D4 is about), ADR-076 (which found trait as a name)
Part I 4.7 writes
trait Summarize {
fn summary(&self) -> String
}
as its own example, and it was a parse error at the keyword. A trait could be
implemented and not declared: impl Summarize for User worked, so the
traits a program could implement were the ones std and the Rust side already
name.
ADR-074 §4 made it load-bearing: a bound
names a trait, so [T: Summarize] had nothing to name, and generics stopped at
the point where a generic body may move and pass its value and nothing else.
NK1126 said exactly that to the user, which is honest and is not the same as
finished.
Item::Trait holds TraitMethod, which has no body — not an Item::Fn with an
empty block, and not one whose body became an Option. The difference between
“declares this method” and “this method does nothing” is the whole of what a
trait is, and a body that is absent cannot be accidentally emitted.
The emitter gets a second, smaller writer for the same reason. function reads
the ledger for sync, for the Shared count of each position, and for whether
the call can fail — every one of those is an answer about a body. A declaration
has none, so it is written by something that says what a declaration is rather
than by something that omits what a definition has.
[T: Summarize], and several are [T: A + B].Inside the brackets Part I 4.6 already puts the parameter, so the bound goes
where a reader of that page looks for it, and it is a list because + is how
two are written. <T: Summarize> and <T: Named + Aged> are what reach the
generated file, in the order the source wrote them.
A trait’s methods are recorded as Summarize::summary — the same key shape an
impl’s methods get, because a bound and a receiver ask one question: what
does a value of this thing have. So when a member is reached on a type parameter,
the bounds are tried as receivers and the call is asked again on the trait.
The consequence is the point: the bound answers the whole call and not merely
whether it exists. Arity is NK1101 from the declaration, the argument types are
NK1102, the result type is what a let or a return is checked against, and
sync, throws and the binding of the signature’s variables all come from the
same entry — exactly as they would from a receiver whose type was written down.
NK1126 is unchanged where no bound declares the member, which is what keeps it
honest: a bound is not a blanket licence.
Ledger::traits answers the question a bound asks first — is Summarize a
trait at all — and a name that is not in it names none.
For a function the word says Asserted, its absence says No, and
sync::infer then raises No to Inferred by reading the body
(ADR-027). A declaration has no body, so what it says is what
it is: sync where the word stands, and without it a method that may
pause — the reading a function type has (ADR-102 D2,
ADR-109 D1). sync::infer’s graph gets a trait’s methods as
leaves with that answer, and a call through a bound pauses where the
declaration allows it.
Part I 4.7’s program runs, and prints User: Ada. With
ADR-074 that is chapter 4’s generics section finished end to end:
declare a trait, bound a parameter by it, call the method through the bound.
trait is a reserved word, which it was not — ADR-076’s sweep
found it as a name, because let trait = 3 was a legal program that rustc
refused about the generated file. It is the one word in the parser’s
“nothing uses this yet” half that now has a construct, and it is there only
because that is where the alternation’s width is safe.
NK1126’s help text is now reachable rather than terminal. It said “a bound
that says which types T may be is not built yet”; the sentence stays true of
what a bound cannot do and stops being true of bounds.
impl against its declaration. A trait now exists to be
checked against, and nothing checks: an impl Summarize for User that
declares a different signature, or omits a method, is refused by rustc
rather than here. That is the next piece and it is a refusal, which costs
nothing today.trait Summarize { fn summary(&self) -> String { … } } has
no syntax and D1 deliberately made the body absent rather than optional.impl’s own parameter. impl Stack[T] has nowhere for a
: Summarize to stand, because ADR-074 D4 reads an impl’s
parameters off the slots it already writes. A bound there wants a parameter
list of its own, which is that record’s §4 and not this one.[T: A + B] where both name
summary takes whichever bound declares it first. Rust’s own answer is that
the call is ambiguous and needs a disambiguation this language cannot write.fn f(x: Summarize), dyn, and a trait in a field are
all absent; a trait names a bound here and nothing else.Ledger::traits is not written to the
ledger file, so nothing outside the unit can name one. That waits on a trait
being reachable across a package, which is a question about modules.Three things went wrong in the order they had to. The bound parsed and emitted
and the checker still refused the call, because the program’s ledger is
absorbed from the unit’s and traits was the one map absorb_renaming left
behind — visible only as a third resolution pass with an empty map where the
first two had it. Then the call resolved and the lowering awaited a String,
which is D4. Then the fixture itself did not compile, for a reason that has
nothing to do with traits: Part I 4.7’s "User: " + self.username is &str +
String, accepted here and refused below. That is a fourth finding and is in
#207, not fixed here.
Item::Trait and ast::TraitMethod; trait reserved and the item in the
grammar; GenericParam::bounds with [T: A + B]; Emitter::trait_method and
Emitter::bounded, so a fn, a struct and a trait’s own methods all write
their parameters with their bounds; contracts::trait_method and
Ledger::traits, carried through absorb_renaming;
Checker::bound_that_answers, asked before NK1126; and trait methods as
unblocked leaves in sync::infer’s graph. Exercised by nine programs in
crates/nikaia/tests/traits.rs, one of them run to see that it prints
User: Ada.