ADR-107: Text Is One Type, String, Whose State the Compiler Picks — and &str Is the Assertion That It Is a View

Status: Superseded by ADR-282 — its D1-D5 are D1, D2, D7, D26 and D27 there Date: September 16, 2026 Target Version: Nikaia 0.0.8 Refined by: ADR-216 — D3’s copy is written .clone(); .to_owned() is refused naming it, and .to_string() stays as the text form of a value Related: ADR-008 (the three states of a view, and @borrowed), ADR-005 §3 (no copy the program did not write), ADR-094 D2 (&T in a declaration is an assertion the body is held to), ADR-027 (the same pattern for sync), ADR-058 D1 (Bytes, the buffer this makes text behave like), ADR-104 (the foreign boundary, where a copy may be owed)

1. Context

A view is in one of three states — borrowed, tethered, owned — and the compiler picks the cheapest that works; a program never writes the state (Part I 6.6). Bytes is a buffer that behaves that way and a program cannot tell which state it is in. Text is the one place where the program picks the state itself, in the type: String owns, &str views, and a literal put where a String is declared is refused until .to_string() copies text the program already had. Thirteen times in examples/, every one a literal or a view where a String was declared.

The language’s pattern for the compiler decides, and you may pin it is already in three places: sync is inferred and a written sync is held to (ADR-027); @borrowed on a struct turns a tether into a refusal; &T on a parameter is a view the body may not keep (ADR-094 D2). Text gets the same pattern.

2. Decisions

D1 — String is the one text type, and its state is the compiler’s.

A String value is borrowed, tethered or owned by 6.6’s rule — the cheapest state that works, chosen per use — and the program never names the state. A literal stored in a String field is a view of static text and allocates nothing; content_type: "text/plain" is a program. f"…" still builds text, and it is owned because nothing else can hold it.

D2 — &str is the assertion that a value is a borrowed view, and it is held to.

Writing &str — on a parameter, a field, a result — says this must be a borrowed view: no copy, no handle. Where the compiler would need a tether or an owned copy to make the program work, the program is refused at the line that needs it, naming what would have been kept. This is ADR-094 D2’s &T for text and ADR-008 D6’s @borrowed per field, with the spelling the language already has. &str is therefore not a second type; it is String with a promise attached.

D3 — A copy is written or refused, never inserted.

ADR-005 §3 stands. .to_owned() is how a program asks for text of its own — to stop a twelve-byte name pinning a mapped file, or to hand text to something that must own it. Where a copy would be needed and none is written, the compiler refuses and names it. .to_string() is the same call under the name a reader from another language will type.

D4 — What the compiler chose is a report, not a guess.

nikaia explain --tethers prints, for every text value, which state it got and why (Part I 6.6). A hot loop that must not allocate asserts it with &str or @borrowed, and reads the report to see that it did.

D5 — At a foreign boundary the entry says which shape Rust wants, and an owned String there is a copy the program writes.

A described crate’s entry (ADR-104) says &str or String in Rust’s terms. A Nikaia String in any state crosses to Rust &str for free. Crossing to Rust String needs text the callee may keep, so it is .to_owned() in the program or a refusal naming it (D3).

3. Consequences

Every .to_string() that existed to satisfy a type goes; the ones that mean copy this stay, spelled .to_owned(). NK1106’s help line stops saying write .to_string() about a literal. Part III 15.2’s two text rows become one.

What a text value costs: a pointer, a length and a handle where an owned string has a pointer, a length and a capacity — Bytes’s shape, one word wider than a bare view. Comparison and hashing are unchanged. A value asserted &str is a pointer and a length, as now.

What the assertion buys is the door for the case where average is not good enough: a program that must not allocate says so once, in the declaration, and a later edit that would make it allocate is refused rather than quietly slower.

4. What this does not decide

5. What is built

D1 for literals, by ADR-207 (0.0.183), and not through step 2: a literal where a String is wanted is constructed where the use keeps it and handed over as it is where the use only reads it, which answers the allocation per position instead of per representation. Step 3’s help and the literal .to_string()s in examples/ came with it. What follows is what stood before that, and is still true of every text value that is not a literal.

Nothing. String and &str are two types in the checker, the emitter and std.contracts; a literal in a String slot is NK1106; no text carries a handle. The order of work:

  1. the checker: &str accepted where String is wanted, and String accepted where &str is wanted with D2’s refusal where the value could not be a borrowed view;
  2. the representation in std: text as Bytes is, and the state chosen by the tether analysis of ADR-008;
  3. NK1106’s help and the thirteen .to_string() in examples/;
  4. D5 at the foreign boundary, once ADR-104’s entries exist;
  5. --tethers listing text values.

#200 carries it.

This site is open source. Improve this page.