ADR-353: A Result’s Views Point Where the Body Says, and a Viewed Buffer Outlives Its Owner’s Name

Status: Accepted Date: October 14, 2026 Answers: what a result points into when it may view more than one parameter, and what a caller may do with an argument while it holds such a result Related: ADR-283 D3 (no implicit copy), D13 (the walk), D23, D24 (the keep), D25 (changed by D1-D3 here); ADR-251 D4 (the ledger’s spelling); ADR-282 D13-D16 (text tiers: a position is what flows in); ADR-001 D1 (stable toolchain); Part I 6.5, 6.6, 6.8; Part III 13.5, C.3

1. Question

Where does a result point when the body can hand back views of two parameters, and what does that cost the caller?

struct Pair { left: ref String, right: ref String }

fn pair(a: ref String, b: ref String) -> Pair {
    return Pair { left: a, right: b }      // each field views one parameter
}

fn longest(a: ref String, b: ref String) -> ref String {
    if a.len() >= b.len() { return a }
    return b                               // one of the two, chosen at run time
}

Nikaia promises that nobody writes or reasons about lifetimes: the compiler reads off the body where a result points (Part I 6.5). A rule that refuses these functions, or ties every view of a result to every parameter, puts that reasoning back on the programmer.

2. How others do it

All rows are from memory, not checked against a current manual.

Language A value viewing several inputs One of several, at run time The owner changes while viewed
Rust one lifetime per field if written by hand (Pair<'a, 'b>), else one shared allowed with one written lifetime 'a over both refused: the owner is borrowed; a reassignment drops the old value, so it is refused too
Swift (lifetime dependencies) a dependence on several sources a union of sources refused while the dependent value lives
Mojo (origins) an origin per value a union, ref [a, b] refused while the origin is in use
C++ unchecked unchecked a dangling reference
GC languages the collector keeps every input alive same the old value lives on while referenced

Every language with checked references accepts the run-time choice. None of them lets an owner give its value up while it is viewed; a collector does, by keeping the old value alive, which is what a tether does without one.

3. Options, weighed

Option Why it lost, or won
Refuse a result that may view two parameters (ADR-283 D25) the programmer splits the function or copies: the reasoning about where a view points, done by hand
One source set for the whole value sound, but a view taken out of one field keeps every other field’s source too: a caller holding p.left may not change b
A source set per view, unions where the body chooses at run time chosen (D1, D2)
Name the sources in the signature, -> ref(a, b) String a lifetime with a parameter’s name, the annotation the source never has (Part I 6.5); the ledger keeps the spelling
A copy where a caller would conflict an implicit copy (ADR-283 D3)
Refuse every use of an owner while a view of it lives, as Rust does refuses programs whose data is never in danger: giving a value up moves its buffer, it does not destroy it, where a keep can hold it (D3)
Copy-on-write when the owner changes in place an implicit copy (ADR-283 D3)

4. Decision

D1 — Each view a result holds has its own sources.

Every view a result holds, the result itself or each view inside it (a field, an element, a variant’s value), points into the parameters its ways out reach, by ADR-283 D13’s walk. The sources are kept per view, not per value: pair hands back a Pair whose left points into a and whose right points into b, and a view taken out of left points into a alone.

D2 — Where the body chooses at run time, the view points into the union.

Changed by ADR-views-3 D8: where there is no body (a trait’s method, a function type, a foreign function) the source is written from and required, not taken as the union.

A view that may come from one parameter or another, by two ways out or by one value assigned either, points into each of them: longest’s result points into a and b. The function is not refused. A trait’s method or a function type, which has no body, hands back a view of the receiver, or else of every parameter that is a view.

D3 — A caller may give an argument up while a view of it lives; its buffer moves to the keep.

While a caller uses a view that may point into an argument, the argument’s data stays as it was. Giving the value up is not changing it: a reassignment of the variable, the end of its scope, a move into another variable of the same function, or handing it to a parameter that is lowered as a view (Part I 6.5: the callee neither keeps nor changes it) moves the old buffer into the keep of whatever holds the view (ADR-283 D23), and it is freed when the last such view is gone. No copy is made (ADR-283 D3). A variable the value moved into is followed as the argument was: changing it in place is D4’s.

D4 — Changing an argument’s data in place while a view of it may be used is refused, at the change.

A change in place - a method that changes the value, an assignment to a part of it - of data that a view still to be used may point into is refused with NK2305, at the change. The message names the view, the call that made it, and the way out of the callee that may hand back that argument; it speaks of data, never of how long anything lives. The ways out it names: use the view before the change, or take a copy of it with .clone() before the change.

D6 — Handing the data to a new owner while a view of it may be used is refused, at the hand-over.

Handing the value to a callee that takes it as its own - a parameter that is not lowered as a view, because the body keeps it or changes it, push on a list among them - while a view that may point into it is still to be used is refused with NK2305, at the call. The new owner may change the data or free it, and the caller cannot see when. The message says so in the words of D4: the headline names the value and the view, the labels mark the call that made the view, the hand-over (take keeps y and may change or discard it) and the later use. The ways out it names: use the view before the hand-over, hand over a copy (take(y.clone())), or keep a copy of the view (longest(x, y).clone()).

D5 — The ledger writes each view’s sources.

Changed by ADR-views-3 D10: the ledger writes a source as the source does, -> String from a | b, returns = "left: from a, right: from b".

A result that is a view writes its sources in the signature, -> ref(a | b) String (ADR-251 D4). A result that holds views writes each view’s sources by its path, returns = "left: ref(a), right: ref(b)". A caller reads this and never the body.

D7 — A type has one place for a view per field that holds one, public or not.

A struct keeps apart what each of its fields points into: each field that holds a view, directly or through another type, has a place of its own, in the order the fields are written. A field whose type itself holds several views is one place: what its views point into is joined (a returns path is one field long, D5). The ledger’s tethered lists these fields in that order, and a package that reads the type takes its places from that list; nothing else is written. A pub type has the same places as a private one.

An enum has one place for all its views: a value is one of its variants, a choice made at run time, which points into the union (D2). A type the program throws keeps one place as well (its sum, ADR-280 D15); not decided here whether it should have more.

D8 — A literal that takes a text is a variable of the same function; the position becomes what flows in.

Added October 29, 2026 (#942, #945).

let v = parse(text)                          // v views text
let mut c = Cursor { input: text, pos: 0 }   // text moves into c.input
c.pos = c.pos + 1
println(v.head)

A struct literal, a variant’s value, a tuple or a list built in a function is a variable of that function in the sense of D3. Moving a text into one of its positions while a view of that text is still to be used is giving the text up: its buffer moves into the keep of what holds the view, and the position holds a view of that buffer. The move counts as a view flowing into the position (ADR-282 D13): the field, element or payload is a view where only views and literals flow in, and holds either where text of its own flows in too (ADR-282 D14), across the whole program, as for a view handed in directly. The new place is followed as the argument was: changing it in place while the view lives is D4’s.

The same holds for a text parameter whose body only moves it into such a local value: that is not keeping it (Part I 6.5). The parameter stays a view, the position becomes a view of the caller’s argument, and views the function hands back out of the value point into that parameter (D1, ADR-283 D24).

D9 — Where the position must hold text of its own while a view lives, the move is refused with NK2305.

A position holds text of its own where the program changes it in place, hands it to a callee that keeps it, or keeps it where only text of its own may stand (Part I 2.2) - anywhere in the program, not only in the function that builds the value. Moving a text whose data a view still to be used may point into into such a position is handing it to a new owner (D6) and is refused with NK2305, at the move. The message is D6’s: the headline names the value and the view, the labels mark the call that made the view, the move into the position (Cursor.input must hold text of its own), the place that makes it so (c.input.push_str(..) in advance changes it in place) and the later use. The ways out it names: hand over a copy (Cursor { input: text.clone(), .. }), or build the value first and take the view from its field (let c = Cursor { input: text, .. }; let v = parse(c.input)).

NK1106 (a view stored where text of its own is needed) and NK2105 (a value used after it was handed over) keep their cases where no view of the moved text is still to be used.

D10 — A value whose views point into what the body owns takes the caller’s keep, as a view does.

A struct, a variant’s value, a tuple or a list handed back from a function, whose views point into a buffer the body made or a text moved into it, puts that buffer in the keep of the frame that keeps the result (ADR-283 D10, D23), as a single view handed back does. The result is not refused and its fields are not made text of their own for it.

5. Reasons

6. Consequences

7. What this does not decide

This site is open source. Improve this page.