Status: Superseded by ADR-282 — its D1-D2 are D20 and D21 there
Date: September 26, 2026
Target Version: Nikaia 0.0.209
Answers: ADR-222 §3’s last row, a result both kinds flow
into that hands back a field of a struct parameter, which ADR-223
and ADR-224 carried forward
Related: ADR-094 D2 (a parameter the body does not keep is
lent), ADR-209 (which buffer a view points into),
ADR-012 (a rustc message about a Nikaia line)
ADR-222 left this refused, in rustc’s words mapped to the Nikaia line:
struct Entry { key: String } // mixed: both kinds flow in
fn label(e: Entry) -> String { return e.key } // rustc: missing lifetime specifier
Probing it found the same failure for two shapes no text tier was needed for:
a view field (struct Seen { at: String } given only views) and a list of
views (fn first(xs: Vec[String]) -> String { return xs[0] }). All three are
Part III C.1: a message about a file nobody wrote.
The parameter is lent (ADR-094 D2), so it carries two lifetimes below: the loan and the buffer its views point into. Rust’s elision cannot pick one, and the right one is not the same in every shape.
fn first(xs: Vec[ref String]) -> ref String hands back one of the views
inside xs, not a borrow of the list. So the buffer’s lifetime is named on
the parameter and the result: fn first<'a>(xs: &Vec<&'a str>) -> &'a str,
and fn place<'a>(s: &Seen<'a>) -> &'a str. The caller’s list or struct stays
where it is, lent for the call, and the view outlives the loan exactly as long
as the buffer does.
Only where that parameter is lent and is all the function can borrow from, and is not a view itself: a parameter handed over is one lifetime, which the elision already names. With two, which buffer the result points into is ADR-209’s question, and its refusal already answers it.
String field does.EitherText may own its text, so it cannot be copied out of a loan without an
allocation that nothing in the source shows (ADR-107 D3). Returning e.key
therefore takes e, exactly as it does when key is text of its own:
fn label(e: Entry<'_>) -> EitherText<'_>, one lifetime that the elision
names. The keep inference now reads a
result both kinds flow into as one that hands a value over, not as a view.
ADR-222 §3’s last row is gone, and so is the row ADR-223 and ADR-224 carried.
A caller of label hands its Entry over, which is what it did when the field
was text of its own; a caller of first or place still has its list or
struct after the call.
Found while testing this, and fixed with it: a program declaring its own
struct Seen made the compiler overflow its stack. Seen[T], the lock’s
stamp (ADR-111), was erased to what it stamps even without an argument, which
for a bare Seen is itself. The stamp now needs its argument; a bare Seen is
the program’s type.
All of it, in 0.0.209: the signature writer’s inner_views in
crates/nikaia/src/emit/mod.rs, holds_either in
crates/nikaia/src/contracts/keeps.rs, and the Seen fix in the emitter and in
Ty::is_seen / Ty::unseen. crates/nikaia/tests/text_tiers.rs runs all three
shapes at both settings of user_parallelism; crates/nikaia/tests/seen.rs
holds the struct Seen program.