ADR-224: The Rest of the Text Wall — a Hole, a String?, a List Going in Whole, and the Foreign Boundary

Status: Superseded by ADR-282 — its D1-D4 are D12 (with D22 and D25), D14 (with D5), D16 and D27 there Date: September 26, 2026 Target Version: Nikaia 0.0.207 Answers: #200’s remainder after ADR-223: a mixed position reached only through an f"…" hole, a nullable String?, a container initialised whole with elements of both kinds, and ADR-107 D5’s foreign-boundary copy — under the owner’s Nikaia does not build walls, it takes them down, and the special case pays, not the general one Refines: ADR-223 D2 (its hole exception) and §3 (three of its five rows); ADR-222 §3 (the String? row) Related: ADR-107 D3 (a copy is written, never inserted) and D5, ADR-207 D3 (a String the body only reads is a &str), ADR-068 (the nullable wrap)

1. Context

ADR-223 left four things. Probing them found that two were worse than refused:

struct Entry { note: String? }
let e = Entry { note: text.lines().next() }       // rustc: Option<String>: From<Option<&str>>

let mut b: Vec[String] = text.lines().collect()   // rustc: mismatched types, once b is mixed
let mut c = words(text)                           // a list of views, no annotation
c.push(f"tail")                                   // rustc: expected `&str`, found `String`

Each passed the checker and failed in rustc, about a file nobody wrote (Part III C.1). A String? was not a position, and the checker did not look through the ? when asking whether a view is kept.

2. Decisions

D1 — A String? is a position, and null is no text of either kind.

Everything ADR-222 and ADR-223 decide for a declared String holds for a declared String?: field, result, parameter, annotated let, and the element of a list. Views only make it ref String? (Option<&str> below), both kinds make it EitherText that may be absent. null puts nothing into the decision. A published String? is never both kinds, as D4 of ADR-223 has it.

And the kept-view question is asked through the ?: a view put into a String? that stays text of its own is refused with ADR-208 D2’s explanation, where before it reached rustc.

D2 — One conversion hands a value into a mixed position, and the nullable wrap goes around it.

nikaia_std::either_text::IntoEither hands a value over as it is: a view borrowed, text of its own moved, Option of either kept Option. IntoEitherMaybe does the same into a String?, present or absent whatever the value was. The pass that decides tiers writes value.into_either() (or .into_either_maybe()) into the program; the emitter writes either(value) where it wraps itself (a field, a result, an argument). The checker reads both as the value’s own type, so the nullable wrap ADR-068 decides is written around the conversion — Some(either(v)) — exactly as it would have been without it. No text is copied.

A hole no longer keeps a position text of its own. An argument is wrapped by the emitter, which writes the hole’s code as it writes any other, so a call inside f"{tag(line)}" hands each kind over like a call anywhere else. And a parameter the callee only reads is a &str below whatever flows into it (ADR-207 D3): every caller lends what it has, so nothing is wrapped for it at all — the tier decides only what a keeping parameter is.

D3 — A list going in whole: item by item, or the list it came from.

A converted copy of the list at the let was the other way. It would allocate on every execution of that line with nothing in the source to show it, which is the copy ADR-107 D3 rules out for text, one level up.

D4 — ADR-107 D5 at the foreign boundary is what the described signature says.

A described crate’s entry says ref String or String in the ledger’s language (ADR-104). A view crosses to ref String for free. Crossing to String — text the crate may keep — is refused with NK1102, which says the crate keeps its parameter after the call and names .clone(). The copy is the program’s to write, never inserted. This was already the checker’s behaviour once entries existed; it is recorded and tested here. A described position is not the package’s to decide, so it gets no tier: its representation is the crate’s.

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

4. Consequences

#200 keeps only the published case and a push inside a hole. The README’s wall row names the published case alone. Programs that compiled before compile to the same Rust except that a value going into a mixed let or list is written value.into_either() where 0.0.206 wrote value.into(), and one going into a mixed field, result or argument either(value) where it wrote EitherText::from(value).

5. What is built

All of it, in 0.0.207:

This site is open source. Improve this page.