Status: Superseded by ADR-313 — its D1-D3 are D1-D3 there, its D4-D5 are D7-D8
Date: September 19, 2026
Target Version: Nikaia 0.0.31
Related: ADR-140 D5 (use brings nothing in, for std as
for a package — which left this open), ADR-286 (a package’s
prefix at every use), ADR-286 D1 (use std::… names the
library), ADR-288 D11 (an entry exists because a program asked),
Part I 2.2, Part I 9.1
ADR-140 D5 decided how use works — it brings no name in,
for std as for a package — and said in passing that Vec, String and
HashMap need no use at all, which is the prelude and is unchanged.
Unchanged from what, is the problem. There is no written prelude. The line is
drawn by what the compiler happens to know: crates/nikaia-std/src/lib.rs has
a prelude module, it was grown one pub use at a time as things were needed,
and it carries fs, io, cli, html, task and HashMap — which is to
say a program can read a file without saying so.
A prelude is a promise that is very hard to take back: every name in it is a name some program writes without importing, and removing one breaks that program. So the size of the list matters more than its contents.
Vec, String,
Bytes;println, print, eprintln;assert and panic;And nothing else.
That is the rule the list is built from rather than a property it happens to
have. fs and io leave: reading a file is a thing a program should say it
does, and use std::fs is that sentence. It is also what makes the prelude
safe for a profile with no filesystem (ADR-119) — the list does
not have to be cut down for one.
HashMap is the first thing outside it.It is the name that makes this a decision rather than a tidy-up: a map is
common enough to argue for, and use std::collections is one line, and a
prelude that grows by common enough has no floor. collections::HashMap at
the use, which is ADR-286’s shape one library over.
Additive is the easy direction and it is the one that ends with everything in the prelude. A name in it has to be argued for in a record of its own, with the D2 rule as the bar.
The generated Rust imports what the lowering needs — rt so the fn main
the emitter writes can start the runtime, TrustedMap so a map has a hasher.
Those are not names a .nika file may write, and the two lists are allowed to
differ because they answer different questions: what the program may name, and
what the generated file needs to compile.
use by reading one page rather than
one Rust file.use std::… in front of fs, io,
cli, html, task and HashMap wherever the corpus reaches them, and the
emitter’s prelude split from the program’s.Bytes stays in std or is a language type; it is in the list
because a program cannot do without it, wherever it lives.Vec another program’s mystery.All of it but three names on D1’s own list, which std does not have.
collections, text, foreign — because the
lowering writes the prefix the source wrote.NK1117: std has text::digit_value —
write use std::text, and text::digit_value(…) here); a module used before
it is introduced (NK1117: fs is used here and introduced nowhere),
which is Part I 9.1’s D4 for std rather than for a package; and a type
written without its module (NK1135: HashMap is written without its
module), which is D3..nika corpus already wrote use std::fs and its kind almost
everywhere, so what it came to was two files, fifty-four inline sources in
the test suite and six blocks of the specification.The enforceable form of D1 turned out to be a fact about the ledger rather
than a written list in the compiler: std’s entries that live in a module are
keyed module::name, and the ones that need no use — print, println,
eprint, eprintln — are keyed bare. So the prelude is the bare keys,
and adding a name to it is what D4 says it is: a record, and a line in
std.contracts keyed without a module.
Ledger::lookup had matched on the last segment, which is what made the
question unanswerable: digit_value and text::digit_value were one name. It
matches exactly now, and that also closed a defect of its own — a program with
its own fn read found io::read, in a unit that does not carry the ledger of
the package beside it, and the throws column then spoke for a callee nobody
had resolved.
sleep moved into std::time by D2’s own rule: the rule the list is built
from is that nothing in it pauses, and that is the one entry here that does. So
Part II 12.4’s line is time::sleep(5.seconds()).
HashMap, HashSet, BTreeMap and BTreeSet became
collections::… entries; CStr, Duration, Sender and Receiver went into
the modules they live in; and a type’s methods went with it, because a
method belongs to its type — collections::HashMap::len, the shape
fs::Mapped::deref already had.
A type’s name then carried its module everywhere, and that is what the work
was: the rules that name a type by hand had to ask for the type rather than for
where it is reached from. The copy list, the crossing analysis’s containers, the
indexing rule, the hash a map gets and the C boundary’s CStr all read the last
segment now, through one function that says why.
Two defects fell out of it, both in the ledger’s own parser and both silent:
:, so the receiver
&collections::HashMap[$K, $V] was read as a parameter named
collections whose type was :HashMap[$K, $V]. Every call on a map then met
NK1101 about an argument count nobody had written.) in the text, which is
the result’s where a result has parentheses in it. That one was found a
record earlier, by channel::bounded; this record is where the same shape
appeared twice.Bytes, assert and panic are named by D1’s list and do not exist.
Each is its own piece of work and none of them is this record’s.eprint is on the list and not in D1’s text, which writes only
eprintln. It is keyed bare, so the compiler has it; whether it belongs is a
sentence for whoever revisits the list.TaskHandle and the hulls stay bare. Shared, SharedMut and Locked
are the language’s (ADR-281) rather than a module’s; a
TaskHandle is what a spawn hands back and a program has no reason to write
the name, so moving it would cost a migration and buy nothing today.#190 carries them.