std and the Emitted Code AlikeStatus: 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: the owner’s the minimum has to be in the manifests and the
README, after a crate declared a newer Rust than the one the project promised;
and the owner’s is the codebase compatible with Edition 2024’s stricter
scoping at .await points, or do we need to change them? 1.94 as the new
minimum would be fine
Related:
ADR-001 D1 — the channel is stable, and rust-toolchain.toml alone names it;
ADR-288 D27 — the mechanism: one constant, RUST_FLOOR, written into every generated Cargo.toml and compared with rustc --version before cargo runs;
ADR-277 D11 — the emitted impl AsyncFn and async closures, one of the features the floor has to cover;
ADR-218 — the crates under crates/unsafe/, which carry the floor and the edition too;
ADR-298 — a word the language below reserves is escaped where it is written
Which Rust does Nikaia work with — the oldest version, and the edition — and how is that number kept true?
Nikaia’s compiler is written in Rust, its std is a Rust crate, and every
program it compiles reaches cargo as Rust. All three meet one toolchain on a
user’s machine, so the question has one answer for all of them. It cannot
default: a minimum derived from one language feature and never checked
afterwards is a promise nothing keeps, and the failure it produces — cargo
refusing, or rustc failing on the generated file — is the class
Part III C.1 forbids.
The edition is part of the same question, because it decides what the emitted
code may write and when its values are dropped. Edition 2024 changes two drop
orders, and a drop order matters at .await: a guard or borrow that lives past
a pause is held for the whole pause and makes a future !Send.
The field here is the language below’s own. Cargo reads a crate’s rust-version and refuses an
older toolchain in its own words, and an edition is chosen per crate, so crates
on different editions build together. The edition’s migration lints
(cargo fix --edition) report each place whose meaning changes. Two of the
changes concern drops:
tail_expr_drop_order: the temporaries of a block’s tail expression are
dropped before the block’s locals, not after them.if_let_rescope: the scrutinee’s temporaries in if let … else are dropped
before the else branch, not at the end of the whole if.Both make values live shorter, never longer. So a program that compiled
under 2021 cannot start holding a lock across an .await because of them; what
can change is an order of Drop that someone relied on.
| Option | What it costs | Verdict |
|---|---|---|
A floor derived from one feature (-> impl Trait in a trait method, 1.75) and not checked |
goes stale without anyone noticing: the code came to need NonNull::map_addr (1.84), the emitted AsyncFn (1.85), std::io::pipe (1.87) and a compiler dependency that needs 1.88, while the documents still promised 1.75 |
rejected: a floor is measured, D2 |
A floor per component (compiler, std, emitted code) |
three numbers where one installation holds all three; the lowest promises what the others cannot keep | rejected, D1 |
| One floor, 1.88, measured in CI | a job that runs the suite on an older toolchain | chosen, D1-D3 |
| Raise the floor to 1.94 with the edition switch | locks out 1.88 through 1.93, and nothing measured needs them | rejected, D7 |
| Stay on Edition 2021 | keeps the emitted code on an edition the language below has moved past, to avoid two changes in the emitter and none at a pause | rejected, D5-D6 |
| Edition 2024 for our crates and the emitted code; benchmark crates stay 2021 | two emitter changes (unsafe extern, gen); no .await point changes |
chosen, D4-D6 |
The number that decided the floor: the compiler’s own build needs 1.88, the
emitted code 1.85, and with 1.84 or 1.85 the generated file fails. The number
that decided the edition: the migration lints over every crate of ours found
no if_let_rescope site and ten tail_expr_drop_order sites, none of which
holds anything across a pause.
The compiler, std, the crates under crates/unsafe/, and the programs the
compiler generates all require Rust 1.88 or newer. One number, because one
installation holds all of them: the toolchain that builds Nikaia is the one
that builds what it emits.
scripts/check-floor.sh reads the number from the emitter’s RUST_FLOOR,
installs exactly that toolchain, and runs the whole test suite on it — which
compiles every program the tests emit with it, against a std built with it —
and the tests of every crate under crates/unsafe/. It also checks that every
manifest says the same rust-version. CI runs it as a job of its own. A floor
does not rise unless this passes on the new number; a feature that needs a
newer Rust than the floor fails the job.
rust-version = "1.88" in the manifest of every crate of the compiler and
std (crates/nikaia, crates/nikaia-std, crates/orchestrator) and of
every crate under crates/unsafe/; RUST_FLOOR in the emitter, which
ADR-288 D27 writes into every generated Cargo.toml and
compares with rustc --version before cargo runs; the getting-started
instructions; Part III 13’s prerequisites; the comment in
rust-toolchain.toml, which names the channel and not the floor
(ADR-001 D1).
.await point needs a change for Edition 2024.The edition’s drop-order changes make temporaries live shorter. None of the
places they touch holds a lock or an observed Drop across a pause, and the
suite, which runs the runtime’s async paths, passes unchanged.
This covers crates/nikaia, crates/nikaia-std, crates/orchestrator and
every crate under crates/unsafe/. The benchmark crates under benches/ stay
2021: they are measurements, not the product, and an edition is per crate.
Every generated Cargo.toml, the grammar runner’s manifest and the tests’
rustc --edition say 2024. The emitter writes unsafe extern "C" { … }, which
the edition requires. gen is a reserved word of the edition, so it joins the
emitter’s list of words the language below reserves and a Nikaia name gen
reaches Rust as r#gen (ADR-298); the sweep in
tests/reserved_below.rs compiles it escaped in every position.
Edition 2024 needs Rust 1.85 and let chains need 1.88, which is already the floor (D1). Nothing the edition requires needs a newer Rust, so the floor stays 1.88 and D2’s job runs on it with everything on 2024.
cargo runs.if let were nested,
rustfmt’s 2024 import order — changes no behaviour.union is accepted by Rust as an identifier today; the sweep in
tests/reserved_below.rs is what will say otherwise (ADR-298).