ADR-226: A View Handed Back Out of a Parameter That Holds Views Is the Buffer’s

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)

1. Context

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.

2. Decisions

D1 — A view handed back out of the one parameter that holds views is the buffer’s.

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.

D2 — A mixed field handed back takes its struct, as a 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.

3. Consequences

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.

4. What is built

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.

This site is open source. Improve this page.