ADR-313: The Prelude Is a Written List, Small and Closed — What Is the Language’s Needs No use, Everything Else Is Written With Its Module, and Each Spelling of the Other Is Refused

Status: Accepted Date: October 3, 2026 History: the records this one replaces are archived in docs/history/adr; read them only for how the decision was reached Answers: which names a program may write with no use, why those and no others, and what happens to a name written on the wrong side of that line; #190 for the listed names std did not have Related: ADR-286 D10-D14 (use makes a package reachable and brings no name in; a prefix must be introduced), ADR-140 D5 (use brings nothing in for std as for a package, and a module nothing describes yet is left alone), ADR-281 D1-D2 (which shape a shared value gets is the compiler’s; a hull is made by a call), D17-D19 (the doors over several locks, and their order) and D28 (taking a lock inside a lock is refused), ADR-283 (Bytes is the language’s buffer), ADR-293 D6 (panic is on the list), ADR-269 D2 (assert is a prelude function the compiler knows), ADR-119 (a target with no operating system), Part I 1.3, 2.2, 6.2 and 9.1, Part II 12.2-12.4, Part III 17.1 and C.4

1. Question

Which names need no use, and what decides whether a name is one of them?

use brings no name in, for std as for a package (ADR-286, ADR-140 D5). So every name a program writes bare is a promise that is very hard to take back: removing one breaks every program that wrote it. Left undecided, the line is drawn by whatever the compiler happens to know, which grows one name at a time as something is needed - and then a program can read a file without saying so, and a reader cannot find out what needs no use without reading the compiler.

The question has two sides that a single answer has to cover: which names are on the list, and what a program meets when it writes a name on the wrong side of it - a module name without its prefix, or a listed name with one.

2. How others do it

No other language is weighed; the comparison is with a list nobody writes down - a Rust prelude module in nikaia-std whose pub use lines are the list. Its cost is the reason for D1: what such a list carries is not argued for, and nothing says when it is complete.

What does not transfer from a host language’s prelude is its reason for holding a name. A name here is on the list because the language decides what it does (D4-D6), not because it is common; a prelude that grows by common enough has no floor (D3).

3. Options, weighed

Option Example Why it lost / won
No written list: whatever std exports bare let t = fs::read_to_string(…) with no use lost: the list is a property of a Rust file, it grows by need, and it lets a program do I/O without saying so
A written list, built from a rule use std::fs then fs::read_to_string(…) chosen (D1-D2): a reader reads one page; a name costs a record
HashMap on the list, because a map is common let m = HashMap() lost (D3): common is not a bar, and use std::collections is one line
eprint behind use std::io io::eprint("…") lost (D4): it would say a module decides what it does, and only the compiler knows the target
access_all / update_all behind use std::lock lock::access_all(a, b) … lost (D5): it reads as an alternative to nesting access, which is the reading that makes a deadlock possible
The shared types in a module lock::SharedMut(0) lost (D6): the compiler picks their representation per value; no module owns them
A per-project prelude a manifest key not taken (§6): one program’s Vec becomes another’s mystery
A listed name written with a module left to rustc io::println("x") lost (D9): the reader’s consistent guess ends in an error about a file nobody wrote, and a lock spelt this way types as nothing, so the nesting refusal does not run
Refusing every unknown module prefix use std::db::postgres lost (D9): it would refuse a module nothing describes yet, which ADR-140 D5 leaves alone for Part III C.4’s reason

4. Decision

D1 — The prelude is written on Part I 1.3, and it is this list and no other.

The list is closed: a name not on it is reached through its module, after use std::<module>, the way a package is reached (Part I 9.1). D4-D6 say why the later entries are on it.

D2 — Nothing on the list does I/O but printing, and nothing on it pauses.

That is the rule the list is built from, not a property it happens to have. Reading a file is something a program should say it does, and use std::fs is that sentence. It is also what makes the list safe on a target with no filesystem (ADR-119): it does not have to be cut down for one, so it does not differ by target. By the same rule sleep, which pauses, is time::sleep (Part II 12.4).

D3 — HashMap is the first thing outside it.

A map is common enough to argue for, and that is why it is the line: a list that grows by common enough has no floor. A program writes collections::HashMap after use std::collections, and the other collections and std’s other types live in their modules too, with their methods.

D4 — eprint is on the list, because diagnosis is the language’s.

It is what a program says when something has gone wrong, which is what panic (ADR-293 D6) and assert beside it are for. What it does is not one thing: a file descriptor on a hosted target, a call into the host on WebAssembly, a serial line or nothing at all with no operating system. A module does not know the target, and the compiler does; use std::io in front of it would say it is a library function like any other, which is the one thing it is not. eprintln is on the list for the same reason.

D5 — access_all and update_all are on the list, because safety is the language’s.

They are the doors over several locks (Part II 12.3). Every acquisition takes the locks in one order however the program wrote them (ADR-281 D19), so access_all(a, b) in one task and access_all(b, a) in another take them alike and a deadlock cycle cannot form. That is a property of the language rather than a convenience. They are free functions only because a method has one receiver and these have several; behind a module they would read as an alternative to nesting access, which is exactly the reading that makes the deadlock possible.

D6 — Shared, SharedMut and Locked are on the list, because the compiler picks their representation.

Which shape a shared value gets is decided per value by the compiler (ADR-281 D1), and a type whose representation the compiler picks is not one a module owns - the argument ADR-283 makes for Bytes, one type over. Part I 6.2 defines them in the language chapter, a program makes one with a call and no import (SharedMut(0), ADR-281 D2), and Part II 12.3’s nesting rule is the language’s.

The names that are reachable and stay off the list are the ones no program writes - Entry, TaskHandle, Args: each arrives as a result and none can be constructed.

D7 — A name joins the list by a record, never by being needed once.

Adding is the easy direction, and it is the one that ends with everything on the list. A name on it is argued for in a record of its own, with D2 as the bar and D4-D6 as the kinds of reason that have passed it.

D8 — The emitter’s own prelude is not this list.

The generated Rust imports what the lowering needs - the runtime so the fn main the emitter writes can start it, a map’s hasher, the modules whose prefix the source wrote. Those are not names a .nika file may write. The two lists answer different questions - what a program may name, and what the generated file needs to compile - and they are allowed to differ.

D9 — A name on the wrong side of the list is refused, in both directions, with the spelling to write.

D10 — The list and the compiler are held to each other by a test, read off the page.

The direction a test must watch is a name that is reachable and unlisted, because nobody meets it: nothing refuses it. So a test reads Part I 1.3 and asserts that every function std keys bare is named there, and that every type the checker knows by name (the hulls) is named there. The other direction - a name listed and missing - is one a program meets on its own, at its first use.

5. Consequences

6. What this does not decide

This site is open source. Improve this page.