ADR-003: Two Programs — the Language, and the Machinery That Compiles It

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)

1. Context

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.

2. Decisions

D1 — A frontend hands over Rust source text, and that is the whole interface.

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).

D2 — CLI wrapping, caching and Cargo instrumentation live in a generic orchestrator.

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.

3. Consequences

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.

This site is open source. Improve this page.