ADR-004: One Lowering, and It Emits Rust Source Text

Status: Accepted Related: ADR-003 D1 (the interface this is the Nikaia side of), ADR-001 D4 (Stage 0 is a transpiler — this is that transpiler), ADR-296 (the lowering construct by construct), ADR-300 (diagnostics, which need the generated source to be readable), ADR-021 D9 (how a backend that is not here refuses), ADR-260 D3 (the verbs that lower or interpret one file)

1. Context

A .nika file has to become a program. Nikaia does not own a code generator and does not want one: Rust’s own is better than anything this project could build, and Rust’s borrow checker is what lets Nikaia promise memory safety without implementing it (ADR-002 §3).

So the question is not how to generate machine code but what to hand rustc. Two answers were available — a data structure, or text — and the cost of getting it wrong is the same either way: two ways of producing a program means double the maintenance and guaranteed drift between what is debugged and what is shipped.

2. Decisions

D1 — There is one lowering, and it emits Rust source text.

crates/nikaia/src/emit takes the Nikaia AST and writes Rust, in a single pass. The mapping for a construct — a let to a let, a grammar to a grammar! invocation — exists in exactly one place, and that place is the only code generator in the toolchain. rustc then compiles the text like any other Rust.

This is the whole of ADR-003 D1’s interface on the Nikaia side and the whole of ADR-001 D4’s “Stage 0 is a transpiler”. There is no second exit, no second representation and no alternative to contrast it with: the only other way to run a file is nikaia interpret (ADR-260 D3), which emits nothing at all.

The lowering is syntactic and name for name (ADR-296 D17). Where Nikaia and Rust disagree about what a name means, the emitted program says so by not compiling, rather than the emitter guessing — and ADR-300 is what puts that complaint back on the .nika line that caused it.

D2 — The text a person reads is the text that is compiled.

The generated Rust is not a debugging view of some other artifact: it is what rustc is given, byte for byte, and what --explain maps diagnostics back through. So “readable” is a requirement on the one code generator rather than a second feature that could drift from it.

Two consequences are worth naming, because both shaped the emitter:

3. Consequences

The toolchain depends on rustc the way every program does — a command line and a file — and on no internal of it. A new Rust release costs what it costs any other crate in the ecosystem, and a Nikaia binary links libc and nothing exotic, so it runs anywhere that Rust’s own output runs.

Text is parsed twice: once by the grammar, once by rustc. That round trip is under one percent of the instructions a full compile retires, and the share falls as a program grows, because every .nika file becomes a mod in one Rust crate and the fixed cost is paid once.

This site is open source. Improve this page.