ADR-144: A Name Denotes One Thing, and the Second Declaration Is Refused

Status: Superseded by ADR-286 — its D1-D3 are D4-D6 there Date: September 19, 2026 Target Version: Nikaia 0.0.26 Related: ADR-140 D1 (the brace struct literal, whose build picked this in silence), ADR-133 §5 (whose open question named the shape), ADR-047 D1 and Part I 9.1 (a package is a directory and its files share one namespace), ADR-046 D5 (one name per file, one namespace over), ADR-096 (NK1135), Part III C.1

1. Context

Nothing said a name may denote only one thing, and the compiler behaved as if several rules were in force at once.

Across files it was refused: two files of a package declaring one name is modules.rs’s own message, because the files share one namespace (ADR-047 D1) and rustc would otherwise report a duplicate definition against the generated file.

Inside one file nothing was, unless the build happened to go through a manifest — nikaia --input skips the module layer entirely, and that is the path the whole corpus, the specification’s blocks and every reader’s first program take. struct Foo beside fn Foo, two struct Foos, a struct and an enum of one name, two fn fs: each lowered, and the backend answered.

And the list of what counts was short. fn, struct and enum were counted; a trait and a grammar were not, so trait Foo beside struct Foo was accepted even through a manifest.

Found by building ADR-140 D1, from the case that matters most: a name that is both a type and a function resolves to the type, so Foo(n: 1) is NK1146 and the function is uncallable through the only spelling ADR-133 D1 gives it — with a help that sends the reader to a line building the struct, which is a different program. That record’s own open question had named the shape and neither record took it.

2. Decisions

D1 — A name denotes one thing, and the second declaration is refused.

struct Foo { n: i64 }
fn Foo(n: i64 = 0) -> i64 { return n }   // error[NK1148]

Foo is declared twice in this file: as a struct and as a fn. The caret is on the second declaration, because that is the one that arrived and the one to move; the note names the first.

Two of a kind are the same rule and the same message — two struct Foos, two fn fs — because the reader is doing the same thing either way: finding out which of two declarations a line means, and being told that the question does not arise.

D2 — What declares a name: fn, struct, enum, trait, grammar.

Those five and nothing else. A method is not one — it belongs to its type, and two types may each have a len. A rule of a grammar is not one — it belongs to its grammar, and Json::value is how it is reached (ADR-140 D3). A field, a variant, a type parameter and an impl block declare nothing at this level either.

The anonymous constructor needs no clause. struct Foo puts Foo::new in the ledger and fn Foo puts Foo, so the two never collide there — and they collide here, where a reader reads, which is the whole point of the rule.

D3 — Across files the rule is the same rule, and it keeps its own message.

ADR-047 D1’s refusal stays where it is, because it has something this one does not: two paths to name. What changes is that it counts a trait and a grammar too, and that it no longer has to serve the one-file case — which it did badly, naming one file twice in a sentence about two.

3. Consequences

4. What this does not decide

5. What is built

All of it, and the reason the rule reaches both paths now is that it moved: the per-file case is NK1148 in the checker, which every build runs, where it used to be reachable only through the module layer a --input build skips.

The message names both kinds and the caret is on the second. Foo is declared twice in this file: as a struct and as a fn — and where they are of one kind it says so once, twice in this file, both as a fn, which is the same sentence rather than a second one.

Three shapes were accepted through every path before this, and the list is here because it is what the rule’s absence actually cost: a trait and a grammar were not counted at all, so trait Foo beside struct Foo reached rustc even through a manifest; and everything else reached it whenever the build was a single file, which is how the corpus, the specification’s blocks and a reader’s first program are all compiled.

And modules.rs keeps its refusal for what only it can say. Two files under one name is its message, with both paths in it; it skips a pair that is the same file, because the checker has that one and says it better.

Six tests in crates/nikaia/tests/one_name.rs.

This site is open source. Improve this page.