Status: Superseded by ADR-286 — its D1 is D1 there, its D2 is D15
Related: ADR-046 D1 (what use does; this record decides what
it names, and moves that boundary from the file to the package),
ADR-002 D1 §5 and D4 (the three things the toolchain refuses to
guess at, and the path std already arrives by), ADR-045 §3 (the
portability rule this makes reachable), ADR-043 D6 (the overflow
check, whose foreign/own split D2.5 assigns rather than inherits),
ADR-020 (the ledger a consumer reads).
There is no way for one Nikaia package to depend on another. Part III 13.2’s
manifest shows a Nikaia package beside a Rust one and refuses the first with its
reason: no record names a registry, a name space or a distribution format, so the
compiler does not guess (ADR-002 D1 §5). type = "rust" resolves
through Cargo; std arrives by path into a sysroot. Between them, nothing.
And that is not only a missing convenience. The reason a crossing verdict may
not consult user_parallelism is that a library built at one setting has to stay
usable at the other — the sentence that decided the shape of
ADR-045 D1. It is a correct rule and it should stay. But it protects
a situation nothing can construct: there are no Nikaia libraries, because there is
no way to depend on one.
A second half arrived with the first. With file = module there is no re-export: no way for a unit to offer a name another unit declares. A library is many files, so a consumer must name the file a declaration happens to sit in — the library’s internal layout is its public surface, and moving a declaration between files breaks every consumer.
The files in it see one another with no use at all. 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.
This removes the re-export problem rather than patching it. There is nothing to re-offer, because the names inside a package already share a space — so no new form is needed at all. And a library’s internal file layout stops being its public surface by construction: moving a declaration from one file to another is housekeeping, and breaks no consumer.
The field points the same way. Java, Go and Rust all put the boundary of
privacy and publication above the single file. Python is the one that puts it at
the file, and __init__.py — a front door maintained by hand, forgotten names and
all — is the consequence rather than an accident of it.
The alternative, considered and refused: keep file = module and add a re-export form. Not wrong, and it would leave Part I 9.2 untouched. It costs a new form in a language that has just withdrawn two (ADR-022, ADR-041), and it moves the upkeep onto every library author forever.
What it changes, and this is written down rather than discovered: Part I 9.2’s
privacy boundary moves from the file to the package — a declaration is private to
its package unless it says pub — and two files of one package may not declare
the same name. Those sentences are rewritten rather than extended.
std already arrives that way (ADR-002 D4), so this generalises a
mechanism rather than adding one.
No registry, no name space, no distribution format. The three things ADR-002 D1 §5 refuses to guess at stay open, deliberately. Go went years with the fetch location as the identity; Rust and JavaScript built their registries early and carry decisions made when there were three packages. A registry is built when there are libraries, not before.
Five rules come with it, or they get decided by accident:
use writes the key and the path says where
it comes from. Two libraries that both want to be http are therefore the
consumer’s to name apart, which is the authority ADR-046 D3
already gives them.[build] section is ignored, and the compiler says so. A
package is built with the settings of the program that uses it. Anything else
would put two answers to the parallelism question in one build
(ADR-037 D2).ADR-045’s portability rule becomes testable. It has protected a situation nothing could construct; a path dependency is the smallest change that makes the first Nikaia library the first real test of something so far only argued.
ADR-046’s boundary moves and its rules stand. use still names
a unit and brings nothing in; the unit is a package. D4’s “a prefix must be
introduced” and D5’s “one name per file” are unchanged in what they say and
changed in what they range over.
#229’s cross-file type defect half disappears. Naming a type across a file boundary stops being a thing that happens; what remains is the cross-package case, which is what the entry was always really about.
And #143 does not go away with the boundary. A Shared field’s count is
decided per file, and the files of a package are still several runs of that
analysis rather than one — so the fail-closed floor that keeps two of them
agreeing is exactly as necessary inside a package as it was between two modules.
What D1 changes is only which name the field is written under. Making the analysis
run over a package at once is the follow-up, and it would let the floor be lifted
where the whole package is visible; across a package boundary it can never be.
A registry, a name space, a distribution format and versions are
ADR-103’s: a version resolves through Cargo under the crate name
nikaia_<name>, and D2’s path arm stands beside it.
D1 and D2, and what is left of D2 is depth rather than shape.
A package is the entry’s directory: every .nika beside it takes part, whether or
not anything names it, and they are one namespace. The emitted Rust is one
crate root with every file’s items in it — no mod, no use super::*, nothing to
resolve, because one namespace here is one namespace there. The entry goes first,
being the file that may declare main, and the rest in file-name order, so the
output is a function of the tree and not of a directory listing (Part III 13.5).
Two files declaring the same name is refused here, naming both files, rather
than left to rustc — which would report a duplicate definition against a file
nobody wrote (Part III, C.1).
Outside a project the entry is compiled on its own (modules::collect_one). A
package is a directory of a project, which is what a nikaia.toml declares; a
directory of loose examples is a directory of programs, and compiling one must not
pull in the other ten.
http = { path = "../http" } resolves, relative to the manifest’s own directory.
The package’s src/ is read as a namespace of its own, its items become a
pub mod http at the crate root — so http::serve() in Nikaia is
http::serve() in Rust and nothing resolved it (ADR-011 D2) — and
Ledger::absorb qualifies both the key and the types inside each entry with
the package’s name, a package at a time rather than a file at a time. That last
part is what makes a signature written -> Request inside the package the same
type as the http::Request a caller writes.
A library needs no main.nika. What a package offers is its public surface;
an entry point is what a program has.
All five rules are built, and each is a message rather than a silence:
use
lines are checked against nothing, and a dependency that declares Nikaia
dependencies of its own is refused rather than flattened. That is the depth
limit — one level — and it is the one thing here that is thin rather than
partial: what is missing is the resolution, not the visibility rule.mods of one package and two types of one name.[build] is ignored, and the compiler says so — once per
build, not once per pass, because a note a reader meets three times reads as
three problems.NK1110 fires for the first time. Privacy is per package, so until a second
package existed the check had nothing that could reach it. pub now means
something a program can see, and “secret is private to http” is a real
diagnostic rather than a catalogued one.
And pub on a field means something too. The ledger records it —
fields = ["pub id: i64", "method: i64"] — because a consumer of another package
needs to know which fields it may touch and nothing inside the package does. The
language below cannot help here the way it does for an item: the emitted struct is
in the same crate, so pub on a field buys nothing there. Reading such a
field and giving one a value in a struct literal are both NK1110, and pub is
optional on the way in — a ledger written before the word reads as a type with no
public fields, which is the fail-closed direction (ADR-010 D1).
A dependency’s own dependencies, per rule 2 above.
A version, which ADR-103 resolves through Cargo and which is not built; the manifest says so when one is written.