String Field or Result Is Below What Flows Into It — Text of Its Own, a View, or Either per ValueStatus: Superseded by ADR-282 — its D1-D5 are D12-D13, D22, D14, D17 and D26 there
Date: September 26, 2026
Target Version: Nikaia 0.0.203
Answers: #200’s question — a view kept where a String is
declared: a copy or a tether? — and the README wall that came with it, with the
owner’s rule for the answer: the costs are carried by the special case, not
by the general one
Refines: ADR-107 D1 (the cheapest state that works, chosen
per use), which it builds for a struct’s fields and a function’s result;
ADR-208 D2, whose refusal it narrows to one case (D2);
ADR-208 §3’s first question, which it answers (D4)
Related: ADR-209 (where a view’s buffer lives),
ADR-221 (the same in a container that drops entries),
ADR-207 D2 (a literal is built where it is kept),
ADR-107 D3 (a copy is written, never inserted), ADR-037 D8 (a
published representation is chosen before its users exist)
ADR-107 D1 made String the one text type and its state the compiler’s. What
was built was the literal half (ADR-207) and the half that only reads
(ADR-208 D1). A view put where a String is kept stayed refused:
struct Person { name: String }
fn make(n: ref String) -> Person { return Person { name: n } } // NK1106
fn first_word(line: ref String) -> String { return line.trim() } // NK1104
fn main() { let n = "ada"; let p = Person { name: n } } // NK1106
A fourth shape was worse than refused: Config { host: text.trim() } for a
text the function read itself passed the checker and failed in rustc, about
a file nobody wrote (Part III C.1).
#200 left open whether the answer is a copy or a tether. ADR-107 D3 had already ruled out the copy: one inserted by the compiler runs on every execution of the line with nothing in the source to show for it. So the answer is a view. What was open is what that does to a struct, which has one representation below, when some lines put views into a field and others put text of their own. Making the field a view everywhere would make every line with text of its own find a home for it, an allocation where it escapes. The general case would pay for the special one. The owner’s rule says it must not.
For every field declared String and every function declared -> String, the
compiler reads what every line of the package puts there, before anything else
looks at the program:
| what flows in | below | who pays |
|---|---|---|
| text of its own only, the ordinary case | String, unchanged |
nobody; nothing changes |
| views only (literals count as views of text that lives as long as the program) | a view: ref String, as if the program had written it |
only what ADR-209 already charges a view — nothing where a frame outlives it, a handle where none does |
| both | EitherText: a view is borrowed and text of its own is moved in, each at its own line |
the field alone: one branch on a read; no line pays for another’s kind of text |
A literal alone moves nothing: a field only literals and text of its own flow
into stays String, and the literal is built where it is kept, as ADR-207 D2
has it.
The tier is written into the declared type — ref String, or ref String
marked either — so the checker, every derived column, the ledger and the
emitter all read one answer. The view tier needs nothing new: the struct holds a
view, and ADR-209 and ADR-221 decide where its buffer lives.
How a value’s kind is read. A literal is a view of static text; f"…", a
.clone() and a String handed over are text of their own; a ref String
parameter, a slice, and what a method of text cuts from its receiver (trim,
lines, split, …) are views; a method or function of the program answers
with its own declared or decided result. Anything this walk cannot read is
text of its own, which is what the program was checked as before. A mistake in
that direction leaves the old refusal, never a wrong program.
A pub field of a pub struct, and a pub function’s result, leave with the
package before the programs that use them exist, as ADR-037 D8 says of a
published count. Where the package itself puts only views there, the ledger
says ref String and every user reads that. A mixed representation would need a
type no other package can know how to build. There the view is refused as
before, with ADR-208 D2’s explanation, and that is the one case the refusal is
still for.
nikaia_std::either_text::EitherText.A small type in std, over Cow<str>: From a view borrows it, From a
String moves it in, and a read derefs to str, prints, compares and hashes as
its text. Its to_owned hands back a String, which is what the program’s
.clone() of text is written as below. It is exactly as large as a String.
Each value going into such a field, and each value such a function hands back,
is wrapped where it is written. Nothing is copied: a view stays borrowed, and
text of its own is moved in.
String.ADR-208 §3 left this open because it is inference across statements: the
binding’s type decided by a later line. The walk that reads what flows into a
field reads that too. Where let n = "ada" is kept in a position that stays
text of its own, the binding is declared String, which is what the refusal
used to ask the program to write. The literal is built once, where it is bound,
as it would have been in place. Where the position is a view, nothing changes:
the name stays a view of static text.
nikaia --tethers lists every String field and result that is not text of
its own below, and why, under text (declared String) — ADR-107 D4’s report.
The ADR-209 lines below it say where each view’s buffer lives.
NK1106 or
NK1104 with ADR-208 D2’s words.let. D1 decides fields and
results. A view handed to a String parameter the callee keeps, or bound by
let s: String = …, is refused as before; D4 covers a name bound to a
literal only where it is kept in a field or handed back.?String field or result, and one of a generic struct’s type
parameters. Not decided here; they stay text of their own.fn label(e: Entry) -> String { return e.key } with Entry.key
mixed). The result’s lifetime is ambiguous below: the parameter carries two.
This is the same rustc message a written -> ref String gets today, mapped
to the Nikaia line (ADR-012). No program that compiled before reaches it,
because Entry.key is mixed only where a line puts a view into it, which was
refused. Built since by ADR-226.The README’s wall put a view of text where a String is kept — a field, a
return is gone for the ordinary program. #200 is closed.
ADR-208 D2’s three explanations stay, for D2’s case and for kept arguments.
Part III C.4’s example is a published field. A program that compiled before
compiles to the same Rust: a position only text of its own flows into is the
String it always was.
All of it, in 0.0.203:
crates/nikaia/src/text_tiers.rs: the walk, the fixpoint over fields,
results and the calls and reads between them, the published rule, the
literal bindings, and the rewrite into the declared types. Run at the end of
parse_to_ast, so nothing downstream sees an undecided String.ast::Type::either; the checker accepts text of its own into a mixed field or
result; the emitter writes EitherText<'_> and wraps each value going in,
and each return and tail of such a function.nikaia_std::either_text::EitherText.--tethers reports the tiers.crates/nikaia/tests/text_tiers.rs: the README’s four shapes as one program,
a mixed field and a mixed result, a field that stays String, the report,
and the published refusal. Each program is compiled and run at both
settings of user_parallelism. text_literals.rs and typecheck.rs keep
ADR-208’s refusals on the cases that still have them.