ADR-165: There Is No Postfix ?? — the Three Things One Is Reached For Each Have a Spelling

Status: 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

1. Context

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.

2. Decisions

D1 — The language has no postfix ??, 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.

D2 — ADR-018 D3’s line reads 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.

3. Consequences

4. What this does not decide

5. What is built

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.

This site is open source. Improve this page.