?? — the Three Things One Is Reached For Each Have a SpellingStatus: Superseded by ADR-279 — its D1-D2 are D3-D4 there
Date: September 20, 2026
Target Version: Nikaia 0.0.83
Related: ADR-018 D3 (the line that asked the question),
ADR-089 D1 and ADR-066 D4 (what ?? is and how it
chains), ADR-138 D1 (the four jumps are expressions),
ADR-161 D3 (panic), Part I 2.3 and 3.5,
Part III A.2 and C.2
ADR-018 D3 writes lookup(a.query("id")??) — a postfix ??,
which unwraps the nullable rather than falling back on it. Part I 3.5 defines
?? as null coalescing, a ?? b, and nothing else; there is no postfix form in
that section, in the parser, or anywhere the specification states a rule.
Part III 17.1 had copied the same line and has since been rewritten;
#191 carried the measurement, and
docs/open-decisions.md carried the question.
It is not a rule specified ahead of the compiler — those carry a Status note and this had none. It is an example using a construct the language never defined, which is worse: a reader who copies it gets a parse error with nothing to look up, because the section it would be defined in does not mention it.
??, and the refusal says what to write instead.It breaks the operator it is spelled with. a ?? b is a binary operator: a
value on the left, a fallback on the right. a?? is the same two characters
with nothing on the right, and it would not be that operator with an argument
missing — it would be an unrelated thing wearing its punctuation. One spelling,
two unconnected meanings, told apart by what follows.
And what the second meaning is, is an abort. A postfix unwrap on a value that turns out to be null ends the program, and the only thing in the source saying so is a second question mark. Part I 2.3’s nullable exists so that there is nothing there is an answer a program handles rather than a way for it to stop; a punctuation mark that quietly reintroduces the stop is the one shape that undoes it. Part III A.2 lists what ends a program, and everything on it is written as a word.
So the answer is no, and the argument is that the three things a postfix unwrap is reached for each have a spelling already:
| what is wanted | how it is written |
|---|---|
| a fallback value | a ?? b — Part I 3.5, the operator itself |
| a jump | a ?? throw NotFound, a ?? return r — the four jumps are expressions (ADR-138 D1) |
| I know it is there | a ?? panic("…") — ends the program with the program’s own words (ADR-161 D3) |
Each of the three says in words what a postfix form would have said in punctuation, and the third is the one that matters most: the abort is written, named, and carries a sentence for whoever meets it.
The refusal is where a reader meets the table. Expected one value, or an expression in brackets, with a list of tokens, is true and no help at all to someone who wrote the two characters on purpose. Part III C.2 asks for the reason and one concrete way out; there are three, and the parser names all of them.
lookup(a.query("id") ?? return bad_request()).An ADR is written once, so that record keeps its text and this one carries the correction — the arrangement #191 named when it found the line. What D3 is about is a handler reaching a query parameter that may be absent, and what a handler does when it is absent is answer; the jump form is that, in one line, with no abort in it.
docs/open-decisions.md has no open entry.??, so no program changes. What changes
is what a reader is told who tries.?? keeps one meaning, and the chain ADR-066 D4 allows —
a ?? b ?? c — keeps reading the way every language with the operator reads
it.?? panic("…") is not enough.D1, in both coalescing rules — the body’s and the statement head’s, because
a_head_parses_what_a_body_parses would catch a difference between them.
D2 is this record’s text. crates/nikaia/tests/coalescing.rs holds the refusal
and the three spellings, each of them compiled and run.