String Is a Position — Parameters, Annotated lets, and the Elements of a List or a MapStatus: Superseded by ADR-282 — its D1-D4 are D12-D13, D14 (its hole exception not carried), D15 and D23 there
Date: September 26, 2026
Target Version: Nikaia 0.0.206
Answers: the owner’s Nikaia does not build new walls, it takes down the
ones that stand — stay on “hand a view of text to a function that keeps it as a
String, or bind it with let s: String = …“, which ADR-222
§3 had left refused
Extends: ADR-222 D1 from fields and results to every place a
program declares String
Refines: ADR-208 D2, whose refusal is now only for a
published position both kinds of text flow into
Related: ADR-107 D1 (the cheapest state that works, chosen
per use), ADR-209 (where a view’s buffer lives), ADR-032 (a
hole is code)
ADR-222 decided a field’s and a result’s representation by what flows into
it, and left four places refused because they were not positions yet: an
argument a callee keeps, an annotated let, and — the one ADR-222 did not
name — the element of a container.
Probing the rest of the wall found that part of it was not a wall at all but
defects. These passed the checker and failed in rustc, about a file nobody
wrote (Part III C.1):
let mut names: Vec[String] = Vec()
for line in text.lines() { names.push(line.trim()) } // rustc: mismatched types
let mut counts: HashMap[String, i64] = HashMap()
for w in text.split(" ") { counts[w] = (counts[w] ?? 0) + 1 } // rustc: Borrow<&str>
fn words(text: ref String) -> Vec[String] { … out.push(w) … } // rustc: mismatched types
The checker typed a method’s view of a String it owns as String, so the
view got past the checker and was refused only by rustc.
String.A struct’s field and a function’s result (ADR-222), and now also:
let — by its initialiser and every assignment to it;Vec[String], HashSet[String], HashMap[String, i64]
— by what push, insert, m[k] = v and an initialiser put there.Each position gets ADR-222’s three tiers independently: text of its own only
stays String; views only become ref String, placed by ADR-209; both become
EitherText. So HashMap[String, i64] counting the words of a file is a map
keyed by views of that file, with no key copied, and names.push(line.trim())
is a list of views.
A read hands on what the position is: an element of a list of views is a view, a parameter that is one reads as one. The walk runs to a fixpoint over all of it, as ADR-222’s did.
value.into().For a field and a result the emitter wraps each value itself (ADR-222 D3). For
the new positions the pass that decides the tiers rewrites the value in the
program: value.into(). EitherText borrows a view and moves text of its own,
and which one happens is read off the value’s type below. No copy is inserted.
Except inside an f"…" hole. A hole is code (ADR-032), so what it passes is
a flow like any other and is walked. But it is parsed out of the literal again
by every reader, so this pass has no expression there to rewrite. A position a
value reaches only through a hole is therefore never mixed: it stays text of its
own, and a view there is refused with ADR-208 D2’s explanation rather than
handed to rustc unwrapped.
The checker reads an EitherText position as String. Below it behaves as
one: it is lent as a &str, .clone() copies it, and a method of text reads
through it. What it accepts into such a position is a view as well as text of
its own. ADR-222 built the opposite, reading it as a view and accepting text of
its own; that did not lend a mixed value to a function that only reads, which a
String is lent to.
A pub function’s parameters join ADR-222 D2’s pub fields and results. Where
the package itself passes only views, the parameter is ref String in the
ledger, and a caller elsewhere lends to it. Where it passes both kinds, the
parameter stays String, and the view is refused as before. That is the one
case ADR-208 D2’s refusal is still for, together with D2’s hole.
f"…" hole — D2.?String, and a type parameter’s String — not positions.let all: Vec[String] = mixed.collect() into a mixed element) — the elements
are not wrapped one by one.The README’s wall row about keeping a view in a String is gone: a parameter, a
let, a list and a map join the field and the result. #200
keeps only D4’s and D2’s cases and ADR-107 D5’s foreign boundary. ADR-208 D2’s
three explanations stay and are reached less often; the one for a name bound to
a literal is not reached at all, because wherever such a name is kept as text of
its own, the binding is declared String for it (ADR-222 D4, applied to every
position).
All of it, in 0.0.206: text_tiers rebuilt around positions with a path into
the declared type, the flows of push, insert, m[k] = v, arguments and
initialisers, reads of elements, holes walked, the .into() rewrite, and the
published rule for parameters; Ty::from_ast reading a mixed position as
String, and the checker accepting a view into one. crates/nikaia/tests/text_tiers.rs
runs a parameter, a let, a list, a map and a -> Vec[String] of views as one
program, and a parameter, a let and a list both kinds flow into as another,
each at both settings of user_parallelism.