T?, Because a Key Is DataStatus: Superseded by ADR-293 — its D1-D4 are D1-D4 there
Date: September 16, 2026
Target Version: Nikaia 0.0.8
Related: ADR-080 D2 (a write through the brackets is an
insert), ADR-052 (T? is a type, and ?. reaches through
one), ADR-066 D4 (?? chains), ADR-113 (a reach
is through a view), ADR-048 D1 (an index is an i64),
ADR-011 D2 (the emitter does not know types), Part I 2.3 and
4.5, Part III Appendix A
One pair of brackets on a map did three things. scores["Player1"] = 100
inserted. scores.get("Player1") answered a T?. And scores["Player1"]
read aborted the program where the key was absent, as Rust’s Index and
Python’s __getitem__ do.
The third is the one that does not belong in this language. T? exists so
that absence is a fact in the type and the compiler makes the program say
what happens then, instead of the program finding out from a customer. A map
is where absence is ordinary: a key comes from a request, a file, a
person, and whether it is present is a property of the data and not of the
program. The one place absence is most common was the one place a spelling
went around the type and crashed. Rust avoids the asymmetry by refusing
m[k] = v altogether; this language had added the write and kept the abort.
A list is different, and stays different. xs[i] with an i outside the
list is a mistake in the program — a wrong loop, a wrong sum — and Index Out
of Bounds stands in Appendix A among the states that are a bug rather than a
condition of the world. An index is arithmetic the program controls; a key is
data it does not.
m[k] reads a T?.let n = counts[fragment]?.n ?? 0
let user = users[request.query("name")] ?? return respond_404()
let counts = report.paths[path] ?? panic(f"{path} was a key a moment ago")
The bracket says what get says, and get stays, saying the same. ?.
reaches through the result and ?? ends the chain, as Part I 3.5 has them.
A program that knows the key is present says so on the right of the ??,
and the abort it used to get for free is the one it now writes.
m[k] = v inserts or replaces (ADR-080 D2). m[k] += 1 reads
the slot and writes it, and the read is a T?, so the form is
m[k] = (m[k] ?? 0) + 1 — the fallback says what an absent key counts as,
which is the question ADR-080 left open and this answers. A
+= on a map slot is refused with that line in the message.
xs[i] is a T, and an i outside the list ends the program as Appendix A
says. The line between the two containers is the line between arithmetic and
data, and it is drawn once.
m[k] lowers to nikaia_std::index::get(&m, index::at(k)), one trait with
an output type per container — a T for a sequence, an Option<&V> for a
map — so that the emitter, which does not know types, writes the same three
tokens for both and the language below picks (ADR-080 D2’s
arrangement, read side). The checker, which does know the container, types
the expression. The value reached is a copy where V copies and a view of
the map otherwise (ADR-113 D2).
null the
compiler made the program answer.examples/access-log/src/main.nika and
examples/1brc.nika, each gaining a ?? whose right side states what was
only assumed. examples/k-nucleotide/src/main.nika’s counts.get(fragment)?.n ?? 0
may become counts[fragment]?.n ?? 0 and means the same.NK1125 (a plain . on a T?) now reaches m[k].field, which is the
refusal that turns the old crash into a message.index::get trait beside index::set, the
checker typing a map read as T?, the message for +=, two example lines.entry-style door — the slot, made if absent — which m[k] = (m[k]
?? 0) + 1 looks up twice. A record of its own if a program needs it.T? by choice; get on a list answers
that already.Nothing. A map read through the brackets lowers to Rust’s Index and
panics on an absent key; += on a map slot goes through the same Index.
The order of work:
nikaia_std::index::get with an output type per container;T?, and the += refusal;null.#261 carries it.