?? Joins Two Views, And A ?. Takes One Of A Binding And Not Of A TemporaryStatus: Superseded by ADR-278 — its D1-D3 are D19, D20 and D21-D22 there; its D4, a test migration, is not carried
Date: September 22, 2026
Target Version: Nikaia 0.0.142
Answers: open-decisions.md’s what does ??
mean when its left side is a view? — option A, the one that page
recommended and the owner took
Closes: the second and third thirds of ADR-113, narrowing
#192 to one shape
Related: ADR-190 D2 (which filed the question),
ADR-008 D2 and D5 (the lattice, and no inserted copy),
ADR-184 D2 (a view of String is str below),
ADR-094 D4 (a let over a place is a view — the same sentence
one construct away)
ADR-190 D2 established that the last third of
ADR-113 waits on one question rather than on a representation:
what ?? means with a view on its left. The options were A (join two views,
refuse the mixed form), B (leave it) and C (widen to the fallback, inserting a
copy). The owner took A.
And building A in its literal form opened a new hole, which is why this is a record and not a patch. Measured:
let name = or(find(1).as_ref().map(|it| it.name.as_str()), || "nobody");
// ^^^^^^^ temporary value is freed at the end of this statement
A view of a temporary dies at the ;. Binding it is rustc’s temporary
value dropped while borrowed about a file nobody wrote — the very class
ADR-113 exists to close, reopened one shape over.
And the obvious guard was the wrong one. emit::is_a_place asks whether an
expression can be written to, which a ?. reach can; find(1)?.home?.city
passes it at every link and roots in a call. What this needs is a different
question.
?. takes a view of a receiver that roots in a binding, and leaves a temporary owned.check::roots_in_a_binding follows the chain down — a name, a field of one, an
index of one, a ? or a cast over one — and answers on the root. A root
that is a name is storage the enclosing block owns; a root that is a call is a
temporary.
ref String
is &str below and ref T is &T, so the two accessors are .as_str() and
&, with .as_deref() and .as_ref() where the reach flattens. The state is
Borrowed — the view points into something that outlives the statement,
which is ADR-008 D2’s free case.And that keeps ADR-113 D1’s promise where it means anything.
D1’s sentence is user is usable on the line after user?.name — a temporary
has no line after and no name to be usable by, so there is nothing for the rule
to be about there.
?? joins two views, and a fallback that owns is NK1185.A view on the left and an owned value on the right asks the operator to hand back one value that is both. Three ways exist and the language has ruled two out:
rustc say expected
String, found &str about a file nobody wrote
(C.1).The help carries the way out the program can take —
?? "…" reads the same and costs nothing — which is
C.2’s rule that a way out that cannot be taken is not
one.
.to_owned() and .to_string() are read by name, because no ledger
describes either and the one spelling this whole question is about —
?? "nobody".to_owned() — would otherwise type as ? and walk past the
refusal. That is the same fact contracts::tether::makes_a_buffer already
reads the same way: all four entries of either name hand back owned text.
rustc’s own help for it is consider using a let binding to create a longer
lived value, and that is a lowering this emitter could write — it is
ADR-185 D2’s trick one construct over.
It is not free, which is why it is not here: a ??’s right side is
lazy, so hoisting a receiver out of a ?? find(1)?.name would run find(1)
where today it does not. Deciding where a hoist is safe is work of its own, and
it is a lowering question — not the tether, and not a state
(ADR-190 D1).
One line, in crates/nikaia/tests/nullable.rs:
u.home?.city ?? "nowhere".to_string() becomes ?? "nowhere". Nothing in
examples/ or benches/ writes the shape at all — the corpus’ one ?. reaches
an i64. The .to_string() that stood there was only ever matching a left side
that used to be owned.
?. takes nothing now
holds for a reached method, for a member that copies, and for a member that
does not where the receiver roots in a binding.NK1185 is nearly free today, which is
the cheap case for a refusal: before this record nothing
produced a view on the left of a ??, so no program could be written that it
would reject. A refusal is free before programs exist and breaking
afterwards — this is the before.?. still has that Part I 3.5
does not describe.?? should join a view to an owned value at all. It should not —
that is D2, and C was the rejected option.All four, at 0.0.142.
Ty::as_a_view, the normalisation both type doors already did, in one place a
third can call.check::roots_in_a_binding and check::Viewed, with
Checked::viewed_reaches carrying which of the two accessors the emitter
writes — the way ADR-028 hands over every answer that emitter
has no types for.Checker::a_fallback_that_owns_what_the_left_side_views → NK1185.crates/nikaia/tests/nullable.rs: the view runs with the receiver read on
both sides of it, the temporary case is pinned as a lowering, and the refusal
is held together with the way out being accepted — which is the half that
makes it a way out.