ADR-190: The Tether Is Needed At An Escape And Nowhere Else — ?. Has No Escape In It

Status: Superseded by ADR-278 — its D1 and D4 are part of D17, its D2 is answered by D20, its D3 is part of D19 there Date: September 22, 2026 Target Version: Nikaia 0.0.141 Corrects the reason of: ADR-189 D3 and §4, whose conclusion stands and whose stated cause does not — the record is not rewritten Related: ADR-008 D2 (the lattice, and what escapes means), ADR-113 D2 (a reached member that does not copy is a view), ADR-156 D4 (NK2303, the refusal that stands where Tethered would)

1. Context

ADR-189 put the third of ?. — a member that does not copy — behind #196, the tether. The owner asked the right question about that: the tether is only needed at very specific boundaries, isn’t it?

It is, and ADR-008 D2 says so in its own table: Tethered is chosen when the value escapes the buffer’s owning scope, which means returned past it, captured by a @detached lambda, or written into a longer-lived container or a global. A struct field is not an escape by itself — that sentence is what makes the 1BRC inner loop free.

None of that is a ?..

2. Decisions

D1 — Measured: three of the four shapes need no state at all.

The lowering ADR-113 D2 asks for is x.as_ref().map(|it| &it.a). Against this repository’s toolchain:

shape what happens state
receiver is a local, the view stays in its scope compiles, runs Borrowed
receiver is a temporary, the view is consumed in the same statement compiles, runs Borrowed
view ?? view — user?.name ?? "nobody", and a literal is a view compiles, runs, zero cost Borrowed
view bound past a temporary — let n = find(1)?.name rustc: temporary value dropped while borrowed an escape

Only the last is an escape, and it is not Tethered either: it is the case NK2303 already covers one construct over — a view of a buffer that does not outlive it, refused in this language’s words and naming .to_owned() (ADR-156 D4). This compiler already builds that answer twice.

So no part of ?. needs the representation. ADR-189’s §4 said it waits on #196 with everything else that wants a state between Borrowed and Owned, and that sentence is what this record takes back.

D2 — What actually blocks it is one question about ??.

A view on the left of a ?? and an owned value on the right: user?.name ?? "nobody".to_owned(). Measured, the two cannot be joined:

That is a decision about what ?? means over a view, not a representation, and it is the owner’s: it decides whether ~6 sites in crates/nikaia/tests/nullable.rs drop a .to_owned() they only ever needed because the left side was owned. It goes to open-decisions.md in that page’s shape.

D3 — ADR-189 D3’s conclusion stands; its reason is replaced.

The third case still lowers as it did and is still not refused — that half was right, and for the reason it gave: refusing find(1)?.name ?? "nobody".to_owned() would refuse a program that compiles and runs today (C.4). What changes is the cause written beside it: it waits on D2’s answer, not on a representation.

The record is not rewritten (ADR-187 D4’s rule, met a second time inside one day): a record says what was decided when, and the correction goes to the living sentences and to the index’s superseded and narrowed table.

D4 — What the tether is still needed for is written down, so this is not read as not at all.

ADR-008’s own worked check names the one shape in that program: a Summary returned past main’s scope — a container of views outliving the buffer they point into. That, a parser handing its rows past the buffer’s scope, and a @detached lambda capturing one are what Tethered is for.

And nothing in the tree is one, which the analysis has already measured: every view in examples/, benches/ and tests/ solves to Borrowed, and crates/nikaia/tests/tethers.rs holds that as a ceiling over the whole corpus.

3. Consequences

4. What this does not decide

5. What is built

The correction, at 0.0.141.

This site is open source. Improve this page.