Status: Accepted Related: ADR-004 (the lowering D1 asks a frontend for), ADR-296 (the Nikaia-side lowering), ADR-021 (D2’s build cache), ADR-002 D1 (the Cargo wrapping D2 makes generic)
Building the prototype as one program showed three costs. The language frontend needed refactoring whenever the backend changed. Every future language would have to re-implement the cargo-wrapping and compiler-bootstrapping layer. And testing the parser meant starting a whole Rust compiler.
All three come from one mistake: the language and the machinery that compiles it were the same program.
A language frontend’s output is Rust source text; the machinery downstream of it takes bytes and knows nothing else about the language. Nothing crosses that line but text — no compiler data structure, no shared type, no version that has to agree.
That is what makes the two sides independently testable and independently
replaceable. A frontend is tested by asserting on the Rust it emits, with no
compiler in the loop at all; rustc is reached the way every other program
reaches it, through a command line and a file. A language designer never touches
a compiler’s internals, because there is no interface through which they could.
The price: text is parsed again at the far end, and a construct with no direct Rust spelling must be lowered into ones that have it rather than handed over as a structure for somebody else to interpret. The round trip costs under one percent of a compile (ADR-004 §3).
orchestrator owns RUSTC_WORKSPACE_WRAPPER injection, incremental build checks
and the build switches, and offers a LanguageFrontend trait — D1’s interface,
as a type. A language’s CLI is a shim that instantiates it.
This is the part ADR-002 would have duplicated per language. What the cache actually keys on is ADR-021.
Adding a second language needs a parser, a lowering and nothing else: the cache,
the lockfile, the manifest translation and the cargo driving are already
written and are not Nikaia’s.
The crates live in one workspace, not two repositories. The boundary is
enforced by the dependency direction: nothing Nikaia-specific appears in
orchestrator, and it does not depend on the nikaia crate.