Status: Superseded by ADR-298 — its D1-D4 are D9-D12 there Date: September 14, 2026 Target Version: Nikaia 0.0.8 Related: ADR-011 D2 (the emitter writes a name for a name and resolves nothing — this is one of that decision’s bills), ADR-051 D1 (reserving a word costs nothing before programs exist — and why that rule does not apply here), ADR-056 (a rule and not a list, at the same boundary one position over)
fn main() {
let type = 3
println(f"{type}")
}
lowered to let type = 3;, and rustc answered
error: expected identifier, found keyword `type`
help: escape `type` to use it as an identifier
about a file nobody wrote, with advice that means nothing in this language.
Part III C.1 calls that a bug in this compiler, and it
is one this compiler had in every position a name can stand in — a local, a
parameter, a field, a function’s own name, a struct, an enum, a variant, a
lambda’s argument, a for binding.
Not the instance but the class, and the class had to be measured twice. The
first sweep lowered let W = 3 for every Rust keyword Nikaia does not reserve
and read rustc’s “found keyword”: twenty-four words. It was wrong by three.
crate, super and box fail at a let with a different message each —
E0532 about a module, E0433 about leading super, “expected pattern” — so
a sweep keyed on one message missed them. Twenty-seven words, found by
compiling every word in every position instead, which is the shape §6’s test now
keeps. gen and union are the only candidates Rust accepts as a name.
type becomes r#type at every position the emitter writes a name, and stays
type in the source, in every diagnostic, and in the ledger.
The obvious alternative is to reserve those twenty-seven words in Nikaia, and
it is wrong. ADR-051 D1’s rule — reserving costs nothing before
programs exist — is about words this language needs for its own constructs,
and every word it has reserved is one Nikaia itself uses. These are not:
type is the field name of every tagged record anybody has ever written, and
move, ref, static and final are ordinary words. Reserving them would let
the backend decide what this language’s vocabulary is, which is the same mistake
ADR-054 D1 refused in the other direction when a cast could name
any type the language below has.
The cause is ADR-011 D2 — the emitter writes a name for a name — so the fix belongs where the name is written. That is ADR-056’s a rule and not a list applied at the same boundary one position over: there it was a relayed message naming a type the program never wrote, here it is a relayed name.
text.One line in Emitter::text would have covered every position at once, and it
would have been a silent defect. text is also what the checker’s recorded
answers are keyed by — pausing_methods, fallible_methods, nullable_fields
are all keyed by the name the source wrote — so escaping there would make every
one of those lookups miss, on exactly the programs this exists for. The two uses
of one name stay two: escaped(name) is written, name is looked up.
crate, super, self and Self are refused, because there is no escape to write.Rust’s answer to r#crate is “crate cannot be a raw identifier”, and the
same for the other three. So D1’s one rule has one forced exception, and it is
the target’s exception rather than a choice made here: NK1128 refuses those
names at every position that declares one. self keeps NK1119, which says
more — it is a reserved word of this language, with a reason of its own.
Three words rather than twenty-seven is the whole point of D1. crate and
super name a module tree this language does not have, and Self is already how
it writes the type an impl is on, so the vocabulary this costs is one nobody
reaches for.
NK1119 and NK1128 are asked at the same positions, so they are asked by one
call (Checker::nameable) rather than two. A position added later is added to
both, which is the failure that was waiting: not_self never covered an item’s
own name, because struct self cannot parse — and struct crate parses
fine and went straight through.
struct Event { type: String } compiles and runs, and prints what was put in
it. That is the program this was worth doing for.
The generated file says r#type, which a reader of it sees and nobody else
does: the source map is by line (ADR-044 D1), and a rustc
diagnostic that survives to the user names the line rather than the identifier.
Three names are gone from the language, and Part I 2.1’s table does not list them, because they are not reserved words of this language — they are refused by a rule about the target. The diagnostic says exactly that rather than claiming they are keywords here.
crate → some mangled name
would keep all three words available, and would put a name in the generated
file that the program never wrote — which is the C.1 class arriving by another
road the moment rustc mentions it. D3 refuses instead; if the three words
ever matter, that trade is the question to reopen.x.type() on a std type is still whatever the
ledger says, and no Rust method is spelled as a keyword.gen and union. Rust takes both as identifiers today. If a future
edition makes either a keyword, the sweep in §6 is what will say so — it
compiles rather than asserting a list.The first measurement reported twenty-four words and was wrong by three, because
it read one rustc message at one position. The sweep that replaced it compiles
every candidate in every position and asks only whether the program builds,
which is the property actually wanted. Two of its own first versions failed for
reasons that were the test’s: a local shadowing the function of the same name,
and a let where the program needed let mut. Both are written into the test,
because a sweep that is the coverage proof has to be one a reader can trust.
emit::escaped and Emitter::name over a twenty-five-word list, applied at
every position the emitter writes a name; Checker::nameable, which asks
NK1119 and NK1128 together, called from the six declaring positions it
already had and from the item names it did not. Exercised by
crates/nikaia/tests/reserved_below.rs: the twenty-five words each written in
every position and compiled, the two Rust accepts checked not to be escaped,
the three it cannot escape checked to be refused at each of eight positions, and
one program with a field called type run to see that it prints click 7.