ADR-213: A Key Is Not a Position, and What Is Handed Over Is Gone — Maps With Owned Keys, and ADR-094’s Refusal Built

Status: Superseded by ADR-293 — its D1-D4 are D27-D30 there Date: September 25, 2026 Target Version: Nikaia 0.0.189 Answers: the owner’s yes to repairing the wall ADR-212 §5 found first — writing into a map keyed by an owned String reaches rustc — and what was found next to it on the way Builds: ADR-094 D2’s last paragraph (a caller that hands a value to a keeping callee and uses it again is refused with the clone named as the way out), decided and never built Refines: ADR-080 D2 and ADR-114 D1/D4 (what goes into a map’s brackets), ADR-207 D1 (the positions that keep a literal) Related: ADR-212 D4 (the takings NK2702 sees, whose machinery this shares), ADR-055 §6 (NK2101, the same question for a task), ADR-208 D2 (why a view kept needs .to_owned())

1. Context

Every example keys its maps by ref String, so nothing in the corpus wrote a map whose keys it owns. Written, each of these went to rustc:

  program before
a m["a"] = 1 on a HashMap[String, i64] mismatched types: &str where the map keeps String
b m[name] = 2, name: String the trait At is not implemented for String
c m[name] ?? 0, name: String the same
d ids[k] = … on a HashMap[i64, String] the key made a usize
e ids[k] ?? "-" on a map of text &String: From<&str> is not satisfied
f s = "b" for s: String, or ids[k] = "seven" NK1105: this is ref String — refused, though every other place that keeps text builds the literal
g xs.push(name) then println(name) borrow of moved value

(a) to (d) have one cause: the brackets wrap whatever is in them in index::at, which exists for positions — it turns a number into a usize and passes a view through — and a map’s key is not a position. (g) is not about maps at all, but a map write is one of its places: the map keeps the key.

2. Decisions

D1 — A key goes into a map as a key: handed over when written, lent when read.

For a map whose keys are owned (not a view), the checker tells the emitter, per key, what the brackets do, and index::at is not written:

A map keyed by views is left exactly as it was.

D2 — A literal kept by an assignment is built there; a map’s text read with a literal after ?? is a view.

An assignment keeps what it is given, so s = "b" and ids[k] = "seven" build the literal into a String, like a let, a field, a return and an argument already did. And m[k] ?? "-" over a map of text is a view of text: the map hands out its String by reference, the literal is a view, and the answer is whichever of the two there is — nothing copied, nothing allocated (index::Or<&str> for Option<&String>). A place that keeps the answer as a String is then told so in ADR-208’s words.

D3 — Data used after it was handed to something that keeps it is NK2105.

The places a value is given away rather than lent: an argument the callee keeps (the & is not written — a method’s argument, or a function’s parameter whose keeps says so; not a mut one, which is &mut below), a key or a value written into a map or a list, a field of a struct being built (unless the field is declared a view), an element of a list or a tuple literal, a let that names it again, and an assignment. At each, a name whose type moves is recorded — a number, a bool, a char, a view and a Seen stamp over any of those are copied, which is NK2101’s rule for a task.

A later read on the same path is NK2105, with .clone() at the hand-over as the way out. As for a sequence (ADR-212 D4), a name from outside a loop or a lambda handed over inside it is refused where it is handed, unless the loop gives it a new value or may leave on that turn; two arms of one if or match are not one after the other; an assignment between gives it a value again.

D4 — A branch that leaves the function takes its takings with it.

if ended == 0 { refuse(connection, 408, …) return } followed by refuse(connection, 400, …) — the http example’s shape, three times in a row — hands the connection over on a path that then leaves. The statement order alone saw a second use. A walk or a hand-over inside an if or match arm or a catch handler whose last statement is a return or a throw is asked about reads up to the end of its statement and no further. NK2702 gets the same answer, since it had the same blind spot. A break or continue is not this: it leaves a branch and not the function.

3. What is left

The first two and the last are answered by ADR-214, 0.0.190.

4. What it costs

Nothing at run time: a key handed over or lent is what a Rust programmer writes by hand, and index::at on a key was the identity or a type error. The checker records one more list per body, read once at its end.

5. What is built

All of it, in 0.0.189: KeyForm and map_keys from the checker to the emitter, the literal at an assignment, the view after ??, NK2105 at the places D3 lists with the loop, lambda and branch rules, D4 for both NK2105 and NK2702, and crates/nikaia/tests/handed_over.rs, whose programs are compiled and run at both settings of user_parallelism.

This site is open source. Improve this page.