ADR-161: Reading a Map Through the Brackets, Built — and What It Took

Status: Superseded by ADR-293 — its D2-D8 are D5-D11 there; D1 (ADR-114 D1-D4 built as written) is D1-D4 there, its NK1162 in D2 Date: September 20, 2026 Target Version: Nikaia 0.0.78 Related: ADR-114 (the record this builds, D1–D4), ADR-080 D2 (the write, and the question it left open), ADR-008 D5 (a copy is never the compiler’s), ADR-011 D2 (this emitter does not know types), ADR-044 D2 (an abort reports the Nikaia line), Part I 1.3, Part I 2.3, Part I 4.5, Part III A.2, Part III C.1

1. Context

ADR-114 decided that m[k] on a map answers a T? — a key is data and may be absent, so there is nothing there is an answer rather than the end of the program. Its §5 said nothing of it was built, and named the order of work. This is that work, and a record of its own because three things it met are decisions the earlier one did not make.

Two lines in the corpus were the evidence: &totals.stations[name] in examples/1brc.nika and &report.paths[path] in examples/access-log/src/main.nika, both safe only because the key came from the same map a line earlier.

2. Decisions

D1 — The three things ADR-114 decided are built as it wrote them.

m[k] on a map is a T? and on a sequence a T (D1, D3); the write is unchanged and a compound one is refused with the written-out form (D2, NK1162); and the read is one call, nikaia_std::index::get, with an output type per container, so this emitter writes the same three tokens for both and the language below picks (D4).

D2 — ?? joins a view with a value, and the language below decides which.

ADR-114 D4 says the value reached is a copy where V copies and a view of the map otherwise. So a map read is an Option<&V> — copying what the map holds is never the compiler’s to do (ADR-008 D5) — while the fallback is written as the value it stands for: m[k] ?? 0. The two sides do not have the same type.

nikaia_std::index::or is where they meet, as two impls that do not overlap: one over Option<T> and one over Option<&T> where T copies. A number is copied out, a struct stays a view, and the emitter chooses nothing — which is ADR-011 D2’s rule kept on the one side of the read that could have broken it.

Where the value does not copy, the fallback has to be a view too, or the program writes .to_owned(). A Clone there instead of a Copy would put an allocation nobody wrote into m[k] ?? "none", which is exactly what ADR-008 D5 forbids.

D3 — panic(message) exists, because it is D1’s own way out.

ADR-114 D1 writes report.paths[path] ?? panic(f"{path} was a key a moment ago") — the abort it used to get for free is the one it now writes. panic is on Part I 1.3’s list and in Part III A.2’s own list of unrecoverable errors, and it did not exist: the form lowered to a call to a function nothing declares, and rustc said so about the generated file (Part III C.1).

It hands back never, so it stands where a value is wanted, and it reports the Nikaia line through the hook ADR-044 D2 installs. It is not the testing chapter’s assert, and does not wait on it.

D4 — A view of a T? is a nullable view.

&str? is Part I 2.3’s own spelling and is_view and is_nullable are independent flags on a written type for that reason. The checker answered unknown for it, which claimed nothing — so let s = &m[k] followed by s.min reached no NK1125 and was refused by rustc, about the generated file. That was invisible until a map read became a T?, and it was the shape both evidence lines were written in.

D5 — A write through the brackets binds its value first.

m[k] = (m[k] ?? 0) + 1 is the form D2 of ADR-114 hands a reader in its own message. Once the read is index::get(&m, …), that line has a &m inside the arguments of a set(&mut m, …) — cannot borrow as immutable because it is also borrowed as mutable, about a file nobody wrote. A let ends the read before the write begins, which is what a reader would write by hand.

D6 — A read is written (*get(…)), and a map’s answer derefs to the option.

ADR-114 D4 says a T for a sequence, an Option<&V> for a map, and one trait cannot answer both unless the caller writes the same thing for each. So the caller writes a *: for a sequence that is the element in its place — which is what return xs[at] on a -> i64 needs, and what keeps self.bodies[i].vx a field of a Body — and for a map it takes apart a small wrapper whose Deref hands back the option.

That is the one place D4’s mechanism had to be made precise rather than followed. A T for a sequence cannot be a value in the language below: a sequence read is a place, because reading one out by value would move out of the container where the element does not copy. The * is what a place is spelled with.

D7 — a ?? b on a T? is a T, and the checker says so.

It claimed nothing before — and nothing was enough while nothing common produced a T?. The day a map read became one, let s = m[k] ?? panic(…) made s unknown, and s.mean() became one more method call nobody could answer: ADR-028’s arrangement losing a receiver it used to have. The ceiling in crates/nikaia/tests/sequences.rs is what said so.

Only that shape. A ?? over something this checker could not type still claims nothing, which is Part III C.4.

D8 — io::Lines’ step throws io::IoError.

ADR-158 D1 gave every function in std a name for what it throws and missed the one iterator: a step of io::lines yielded the language below’s own error. Nothing saw it while every program that read a line also had a "?" in its set and travelled in the opaque channel. The day one of them had a named channel (ADR-159 D1), the ? the loop’s step takes had a raw error on the left and a named one on the right.

3. Consequences

An absent key is a value. counts["b"] ?? -1 answers -1 where the program used to stop.

The two evidence lines say what they know. Both now read … ?? panic(f"… was a key a moment ago"), which is ADR-114 D1’s own example: the key came from the map, and the line says so rather than relying on it silently.

Five rustc messages about generated files went away (D3, D4, D5, D6, D8), and four of them were only reachable because of the change — which is the shape a record that builds an older one tends to have. Each was found by a test that runs the program or by a ceiling that counts something: reading the emitted Rust would have found none of them.

4. What this does not decide

5. What is built

This site is open source. Improve this page.