use Names One and Brings No Name In; a Dependency Is Found by Path or by Version Through CargoStatus: 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: what a program of more than one file is and how its names
resolve; what use does, and whether a name can be brought in
(#229’s cross-file type
defect); how one Nikaia package depends on another, and where the boundary of
privacy is; how a library that uses a library is built; how a trait a package
publishes becomes callable where it is implemented
(#234); how a package
is found by version (#99);
whether a name may denote two things; what may stand in front of a ::
(#141); what a path
in nikaia.toml is relative to
(#150)
Related:
ADR-002 D1 (one CLI wraps Cargo; the Rust arm reaches it) and D4 (std by path into a sysroot, the shape a package dependency generalises);
ADR-296 D17 (the lowering is name for name: a qualified name reaches the emitter as written);
ADR-020 (the ledger, keyed by qualified name);
ADR-021 D2 and D13 (the lockfile records resolved versions; a cache hit, narrowed by D8 here);
ADR-277 D2 (one form, not two spellings of it — the ground D11 is taken on);
ADR-285 D8 (overflow checks on for the program, off for foreign packages);
ADR-312 D6 (a library built at one user_parallelism stays usable at the other);
ADR-100 (a consumer reads a dependency’s ledger and believes it while its hashes match);
ADR-290 (a described Rust crate);
ADR-140 D5 (a std module nothing describes yet is left alone);
ADR-260 D1-D2 (a file inside a project is a file of that project; outside one it is standalone with no dependencies);
Part I 9.1-9.3, Part III 13.3
What is the unit a Nikaia program is divided into, how does a name written in one unit reach a declaration in another, and how does a unit arrive from somewhere else?
Without an answer a program is one file, a library cannot exist, privacy has no boundary to stand on, and
every name the compiler cannot place is left to rustc, whose message is about
a file nobody wrote (Part III C.1).
| Language / tool | Its answer | What it costs there | See |
|---|---|---|---|
| Java, Go, Rust | The boundary of privacy and publication is above the file (package, crate) | — | D1 |
| Python | The boundary is the file; __init__.py re-offers names by hand |
A front door maintained by hand, forgotten names and all | D1 |
| Rust, Python, Elm | A glob import exists | Rust lints it, Python’s style guide forbids it, Elm discourages it for libraries | D11 |
| Java, Python, Rust, Haskell, Elm, OCaml | A selected import (use a::{B, C}) exists and is withdrawn nowhere |
Two spellings of every imported name; Haskell’s practice moved toward qualified names | D11 |
| Go | No import selection; every name qualified | Fifteen years without serious movement to add it | D11 |
| Go | The fetch location is the package’s identity | Years without a registry | D15 |
| Rust, JavaScript | Registries built early | Decisions made when there were three packages | D15 |
| Cargo | An index, a semver grammar, a lockfile, git with a tag, and package = "…" renaming |
— | D24 |
| Rust | A trait must be in scope before its methods are called | The backend asks for an import Nikaia cannot write | D28 |
What does not transfer: a separately maintained re-export layer (Python), and a registry of Nikaia’s own before there are libraries to put in it.
| Option | What it is | Why it lost | See |
|---|---|---|---|
| File = module, plus a re-export form | Keep each file a module, add a way to offer a sibling’s name | A new form in a language that had just withdrawn two; upkeep on every library author forever; a library’s file layout stays its public surface | D1 |
| A glob import | use pool::* |
Regretted everywhere it exists | D11 |
| A selected import | use pool::{Conn} |
Two spellings of one name across the whole surface — exactly what ADR-277 D2 withdrew. Not irreversible: adding it later breaks no program | D11 |
| A rule for which of two same-named units wins | — | A rule a reader must know to read a line | D14 |
nikaia.toml lists the modules |
As Cargo lists crates | A second place to forget; the use already names the unit |
D1 |
| A ledger per file | — | sync inference would depend on which file was read first |
D7 |
| Privacy re-implemented in the compiler | Instead of emitting pub |
Two enforcers of one rule disagree eventually; NK1110 is a message, not a second enforcer |
D2-D3 |
| Every package flattened into one crate | One crate for the program and its packages | The transitive module is at the root, so rule 2 breaks silently and two packages can want one mod name |
D19 |
| One crate, modules named by package identity | Key becomes an alias | The leak stays; D13’s check must widen to qualified names; pulls the registry question forward | D20-D21 |
| One crate, nested modules | mod b { mod c } |
A package reached through two parents is two modules: breaks D15 rule 3 | D20 |
| An index of Nikaia’s own | — | A second place to publish; not ruled out, since the compiler reads a directory | D25 |
A Rust path kept verbatim |
../../../../../shim |
A number derived from a build layout that already changed once unnoticed | D17 |
| A path head refused on absence | Refuse any unknown head | A module of a package, an item of a foreign crate and an undescribed name look alike from inside the checker; it would refuse correct programs | D34 |
What made D31 answerable: of the seven distinct paths the corpus uses as
values, not one has a head the compiler has not read. D19’s shape was run
as four hand-built crates before it was decided: the transitive crate does not
resolve (E0433), the overflow check reaches a library crate, a crate not on
the list wraps, and the diamond unifies.
The files in it see one another with no use at all; a name declared in any of
them can be written in any other. What the package offers outward is whatever
says pub, in whichever file it is declared. A consumer writes use http and
names the package, never a file inside it; a use naming a file of the
same package is refused. A library’s internal file layout is therefore not its
public surface: moving a declaration between files breaks no consumer, and
there is nothing to re-export. A package is a directory of a project — what
a nikaia.toml declares; outside one a .nika file is compiled on its own
(ADR-260 D2).
pub is emitted as pub.A declaration is private to its package unless it says pub, an item and a
field alike, and pub on a field parses. The rule is put where the language
below can act on it rather than re-implemented.
NK1110: privacy in Nikaia’s words, before rustc is asked.Reaching a private item or field of another package — a call, a field read, a
field given in a struct literal — is NK1110, naming the package that keeps
it. Only a name written prefix::item can come from another package, so the
checker needs only to know which prefixes are packages. A field’s pub is
carried in the ledger, because a consumer needs it and nothing in the package
does.
struct Foo beside fn Foo, two struct Foos, two fn fs: NK1148, the
caret on the second declaration and the note naming the first. Two of a
kind are the same rule and the same message. The rule holds whichever way the
build was started, because it is asked by the checker every build runs.
fn, struct, enum, trait, grammar.Those five and nothing else. A method belongs to its type, a grammar’s rule to
its grammar (Json::value); a field, a variant, a type parameter and an impl
declare nothing at this level. struct Foo’s constructor and fn Foo collide
here, where a reader reads, even though the ledger keys them apart.
Two files of one package declaring one name is refused by the module layer, naming both paths; it counts the same five kinds and leaves the one-file case to D4.
sync is a fixpoint over the call graph, and a graph that stopped at a file
boundary would answer differently depending on which file was read first. The
inference graph is the package; its ledger is written in its own root, modules
in name order.
The parse is what tells the compiler which files the program is, so ADR-021 D13’s on a hit nothing is parsed is narrowed to nothing is parsed twice; a hit still saves lowering, checks and inference. A single file’s key is unchanged.
Each unit is emitted with offsets of its own and the driver stamps them as it
joins the text, so a rustc message about one file is traded back for a place
in that file.
use doesuse makes a package reachable, and does nothing else.Every name from it is written with its prefix, at every use: in a type, a call,
a struct literal, nested in another type. The prefix falls where a name is
written, not where a value is used — d.id carries none. use std::fs is
the one use with a path in it, and names a module of the standard library.
use pool::*, use pool::{Conn} and use http::server are each refused with a
sentence of their own that names use pool and the qualified spelling. Taken on
ADR-277 D2’s ground — one form, not two spellings of it — and
firm because it is reversible: a selected form added later breaks no program.
use pool as p shortens the prefix, once, in one place.It also settles a collision: two packages that both want to be http are the
consumer’s to name apart. The alias is resolved away in the file that wrote it,
and a diagnostic names the package, not the alias.
pool::Conn without use pool is refused, so a file lists what it depends on
at the top; use http where neither a dependency nor a directory of the
project (D37) is called http is refused, naming where a package is declared.
Two units under one name in one file — by alias or by collision — is an error, not a rule about which wins. The alias counts.
http = { path = "../http" } generalises how std already arrives
(ADR-002 D4). A library needs no main.nika.
use writes; the path says only where it
comes from.[build] is ignored, and the compiler says so once per
build: a package is built with the settings of the program that uses it
(ADR-312 D6). std is the one exception: its Nikaia half is
lowered once and linked at either setting, which is sound only because
nothing in it lowers differently per switch, and a test keeps it so
(ADR-002 D4).path in nikaia.toml is relative to nikaia.toml, for both arms of [dependencies].../shim means the directory beside this project’s, for a Nikaia package and
for a type = "rust" crate alike.
path absolute.That is the one narrowing of ADR-002 D1’s verbatim passthrough:
everything else in a Rust entry — version, git, tag, features,
default-features, a package rename — reaches Cargo untouched. It is how the
generated manifest already names nikaia-std.
path is relative to is a property of the file, not of the ecosystem.One rule for both arms, because a difference between them would be about which generated file holds the value.
A build emits a Cargo workspace, always: the project as the binary crate and
every package it depends on through [dependencies], transitively, as a
library crate beside it, each with its own Cargo.toml under its own manifest
keys. The packages inside the project are modules of its crate (D38). A dependency’s files
are still read — for its ledger and the checks — and emitted by its own
member. A type’s identity is the package’s directory, not the key a consumer
reached it through, so a diamond unifies in the checker as in Cargo.
Changed by D38: a package inside the project is a module of the project’s crate, not a crate of its own.
A dependency is emitted in Cargo’s renaming form, c = { package = "…", path =
"…" }, so two packages under one key are in two crates and never meet, and one
package reached through two parents is one crate (D15 rule 3).
A transitive package is not a dependency of the crate that would name it, so
naming it fails to resolve below, reported against the .nika line. D13 does
not have to widen to qualified names.
Each generated Nikaia crate is named on the program’s side of the profile,
[profile.dev.package.<crate>] overflow-checks = true - dev being the one
profile a build has (ADR-002 D5) - beside the rows that
turn the checks off for foreign packages.
nikaia_<name>; the package’s own name is <name>.The prefix is the index’s business: no .nika file, use, diagnostic or
ledger entry ever carries it.
http = "1.2" becomes http = { package = "nikaia_http", version = "1.2" };
"1.2" is Cargo’s semver; { git = …, tag = … } is the same arm without an
index; the path arm stays. The resolved version is recorded in nikaia.lock
(ADR-021 D2). A key is an identifier, because it is a use name.
A published crate holds nikaia.toml, src/*.nika, nikaia.contracts and a
stub Cargo.toml that exists for the resolver only. Nothing lowered is
published; the consumer reads the sources and ledger from where Cargo put them
and emits the package as a member (D19). The stub is never linked.
One without .nika sources or without nikaia.contracts is not a Nikaia
package; one with both is read like any package (ADR-100 D3).
The stub lists them, so Cargo fetches the graph at once; a path dependency’s
nikaia.toml is followed the same way. What the consumer may name is unchanged
(D21).
use for every trait an impl in this unit names with a package in front of it.impl http::Handler for Fixed makes the emitter import http::Handler, so its
methods are callable: Rust needs the trait in scope, and D11 gives the program
no way to write the import. The path written is the import; nothing resolves it.
A :: in the trait’s name is what says it is in another crate. A trait of this
package, and one the compiler reads (impl Error for …,
ADR-023 D3), carry none and are left alone.
allow(unused_imports).A program may implement a trait and never call it; a warning about a line nobody wrote is not one to show.
::A type declared in this package or recorded by either ledger; a
package use introduces (D13); a package the manifest declares; a
crate a description covers (ADR-290); a std module. A
head that is none of them is refused, NK1181, in a call as in a value. A file of this package is never
a head: its names are already in the package’s one namespace (D1). NK1171 is
the other half: a member a known head does not have.
head:: answers for the head.A described crate is a list of its items, with no line for the crate’s own word,
so the last question is a prefix. Without it nikaia describe’s output would be
refused by the compiler that asked for it.
use, never a new file.A head is introduced by use (D13), so that is what the refusal offers:
use {head} - with a [dependencies] line in nikaia.toml where {head} is
a package not yet declared, and nikaia describe {head} where it is a Rust
crate (Part III C.2). A .nika file beside this one
is not offered: it joins this package’s namespace (D1) and makes no head. Where
the use is already written and the path still fails, the head is known and
the name after :: is what is missing - declared in that package’s source, or
misspelt - which is NK1171’s question, not this one’s.
Where a known head is one edit away (fx::read beside std::fs), the help
names it first: did you mean fs? with its use line.
Amended October 6, 2026 (#488).
The list is of permissions: the refusal fires only where all five are silent.
A missing refusal costs a rustc message; a wrong one costs a correct program
(ADR-010 D1’s polarity, applied to a refusal).
D1’s one namespace, read off the other files’ item trees rather than a
ledger, so a private enum in a neighbouring file counts.
Where the type of a receiver or the head of a path is known, the members it
has are the ones written down for it: the program’s own declarations, std’s
ledger, a crate’s description (ADR-290) and an extern block. A
method, an associated function, a module’s function or a field that none of
them holds is refused with NK1171, also where the backend has one by that
name: String::from("x"), "x".as_str(), opt.is_some(), fs::frobnicate(),
and a method a program’s own struct does not declare. The help names the
language’s spelling where there is one ("x", .clone(), != null) and
otherwise the nearest name.
std’s ledger is that list, and it is written by hand. An entry it lacks
is a function a program cannot call, and the refusal is how the gap is found:
the entry is added, with its signature, in the change that needs it. No entry is
inferred from the backend’s surface, so a name Rust has and Nikaia does not
stays refused.
This is not D34’s polarity. D34 is about a head nothing names; here the head or
the type is known, the list for it is complete by definition, and a name that is
not on it is not a correct program. An entry that is on the list with an
unanswered signature (-> ?) is a known name of unknown type, and stays C.4’s
silence until #497
closes it.
The same holds for a type’s name. The types a program writes are the
language’s (Part I 2.2), not the backend’s: a written usize, isize, i8,
i16, u16, i128, u128 or str names nothing and is NK1135, outside an
extern "C" declaration, where usize is C’s size_t (Part III 15.1). The
help names i64 for an integer, String for text.
Decided October 8, 2026 (#509, #470).
src/ that holds .nika files is a package of the project.src/model/ is the package model, src/model/rows/ the package
model::rows: the path from src/ is the name, and nothing declares it, so the
tree on disk and the prefixes in the code cannot disagree. It is reached with
use model or use model::rows, with no [dependencies] entry; the prefix is
the last segment (rows::Row), and as shortens it as for any package. A
dependency and a directory of the project under one name are refused. Whether a
directory is a Nikaia package is read off what it holds, as D26 reads a crate:
.nika sources make it one, with or without a Cargo.toml beside them.
Each package of the project, the root included, is emitted as a mod of the
project’s crate, nested as the directories are, each checked and lowered
against its own entries as the build-time program already writes a dependency
(pub mod <package>, ADR-321 D13). A module is a namespace of its
own, so the separation the directories draw holds; one crate is one run of the
backend for the whole project. A package the manifest marks crate (D40) is a
crate of its own instead.
http may use model while model uses http, as modules of one crate may in
the backend. A cycle through a package marked crate is refused, because crates
cannot depend on one another in a cycle. A cycle of build-time values is not a
question of packages and stays NK1168’s.
[directories] in the manifest treats a directory otherwise."src/fixtures" = "ignore": not read, whatever it holds. "src/assets" =
"resources": not code, published with the package and read by asset(…).
"src/testkit" = "tests": its packages are built only under nikaia test.
"src/ffi" = "crate": built as a crate of its own, which is the way for a
directory whose emitted code cannot share a crate (an extern symbol) or that
holds Rust beside its Nikaia, as nikaia-std does. A directory the table does
not name is D37’s.
D37-D40 decided October 9, 2026 (#538).
An expression whose type the compiler cannot tell is NK1245. Exempt is what
its uses type later (a number literal, an open number, an integer comptime
without a type, ADR-285 D23, ADR-287 D21), what is unknown only because a part
of it is (an operator, a branch, a block, a jump, a panic), a reflected
field’s of in a generic body, and an expression inside which a part was
already refused. Where the file holds another error, NK1245 is not
said. A name is asked where it is bound and not where it is read; what a
number nothing typed passes through (a generic call or member that hands it
back, x ?? 0 over a T? nothing typed) is as open as the number;
asset("…") is its comptime’s type; and a DSL driver’s Self::dsl is each
call site’s own, as field.of is each unrolling’s. A call through a package
whose ledger this build does not read is that package’s build’s to ask, and a
dsl block or grammar entry the lowering refuses by name is left to that
refusal. Part III C.4’s says nothing is withdrawn.
Why. What is not known is not checked, and a mistake there reached the
language below as an error about a file nobody wrote (Part III C.1). #497
measured what failing closed costs and closed the gaps until none was left in
std and ten in the programs; those are refused now and built forward.
A value another refusal left without a type - a name never declared, a
member a type does not have - is unknown wherever it is used, lines away;
saying it again there names the same cause twice, so the other error is said
and what is still unknown once it is fixed is said then.
A name is bound in one place and read in many: the question belongs where
the type would have come from, and asked at every read it is the same cause
again. A number nothing typed is decided by its uses (ADR-285 D1), and what
merely hands it on has no type of its own to be asked for.
Rejected: keep failing open (D34), which leaves every gap silent;
a warning in between, which the five backend errors found on 6 October
would have scrolled past.
Decided October 10, 2026 (#497).
NK1148), a private item or field
(NK1110), a head nothing declares (NK1181), a trait a package publishes
and the program cannot call (D28) — Part III C.1
for each.nikaia_foo — D26
makes it useless as a package, not free. Reversible: the compiler reads a
directory, and where it came from is D25’s business.target/nikaia/build/ is never shipped.nikaia publish command, yanking, ownership, and --locked over a moved
git tag — Cargo’s answers until a program meets a case where they are wrong.io::wobble is the ledger’s question
(ADR-140 D5) — and a package named like a std module.std publishes, overloading, and a let
shadowing an item.