ADR-223: Every Declared String Is a Position — Parameters, Annotated lets, and the Elements of a List or a Map

Status: 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)

1. Context

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.

2. Decisions

D1 — A position is every place a program declares String.

A struct’s field and a function’s result (ADR-222), and now also:

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.

D2 — A value going into a mixed position is handed over as 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.

D3 — A mixed position reads as text of its own.

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.

D4 — What is published is never both kinds.

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.

3. What is still refused, and what is not built

4. Consequences

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).

5. What is built

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.

This site is open source. Improve this page.