Status: Superseded by ADR-293 — its D1-D3 are D31-D33 there
Date: September 25, 2026
Target Version: Nikaia 0.0.190
Answers: the owner’s please fix the open points — ADR-213
§3’s three: two uses within one statement, a field handed over, and the
unnecessary parentheses warning the README named
Refines: ADR-212 D4 and ADR-213 D3/D4 (what a
read after a taking is), ADR-094 D2 (keeps, for a part of a
parameter), ADR-114 D4 (how a read through the brackets is
written)
Related: ADR-182 D2 (a slice read carries no *, which D3
extends to the parentheses)
NK2702 and NK2105 ordered reads by statement: a taking counted against
reads that started at or after the end of the statement it stood in. Three
programs fell through that to rustc:
| program | before | |
|---|---|---|
| a | keep(name, name) where keep keeps both |
use of moved value |
| b | s.count() + s.count() |
the same |
| c | xs.push(p.name) then println(p.name) |
use of moved value |
and two more were found writing the probes for (c):
| program | before | |
|---|---|---|
| d | fn f(p: P) { xs.push(p.name) } |
the parameter came out &P: cannot move out of p.name which is behind a shared reference |
| e | xs.push(p.name) with p a ref parameter or a for binding |
the same |
And every read through the brackets was written (*index::get(…)), so
m[k] ?? 0 and let x = xs[1] carried rustc’s unnecessary parentheses
warning — about a file nobody wrote, and named in the README as something a
tester should expect.
Every read gets its order in the walk, and a taking remembers the order of
the read it took. A read in the same statement with a later order is after it:
keep(name, name) reads the second name after the first was handed over, and
s.count() + s.count() walks the second after the first. The walk reads in the
order the language below evaluates — arguments, operands and struct fields
left to right, a receiver before its arguments. An expression walked twice
counts once, by its address.
An assignment in the same statement gives back what it took, because it
writes after it reads: name = name + "x" is a program.
A read of p.name is a read of that path, not of p. A taking of p.name
touches a later read of p.name, of anything inside it, and of p as a whole;
p.x is untouched, as the language below’s partial move allows. An assignment
to p.name — or to p — gives it back.
Two places refuse or decide where this matters:
NK2106: a part of a value that is only lent here — a ref
parameter or self, a for binding over a list, a let over a place — handed
to something that keeps it. The owner still has the value, so a part of it
cannot be given away; the help names p.name.clone().keeps column):
fn f(p: P) { xs.push(p.name) } owns p now, as return p.name already
did. A field that copies keeps nothing; one whose type this file cannot see
keeps — the column’s polarity.*index::get(…) is written bare. Its parentheses are what makes
(*get(…)).len() the length of the element rather than the element of a
length, which only a postfix needs — and every postfix is written through
one function (postfix_base), which now adds them for a read. As an operand,
an argument, a let’s value or a cast’s, the * already binds tighter than
anything around it.
A read is a few more words in a list the checker already kept, and a path is a string built once per read. Nothing at run time.
All of it, in 0.0.190: the order and the path of every read, first_read_after
shared by NK2702 and NK2105, NK2106, a part keeping its parameter in
contracts::keeps, the parentheses moved into postfix_base, and the tests in
crates/nikaia/tests/handed_over.rs and sequence_shapes.rs — including a
program compiled with rustc’s warnings read back, so that a stray
parenthesis fails a test rather than a tester.