Skip to content

The Curious Case of stdlib: nightly compatibility findings #141

@cds-amal

Description

@cds-amal

Context

I set out to answer a simple question: can stable-mir-json emit smir.json for Rust's standard library? Turns out the answer is yes, but the journey there surfaced some interesting findings about the nightly compatibility story.

What happened

  1. Caught a rustc layout panic. ty.layout() returns Result<Layout, Error>, promising graceful failure. But for certain types (specifically core::fmt::Formatter<'_>, whose &mut dyn fmt::Write field triggers a Sized check on a trait object with escaping bound vars), rustc panics instead of returning Err. I wrapped the call in catch_unwind to keep going (see the fix commit for the full call chain analysis). This is arguably a rustc bug; the Result contract should have caught this before it became a panic at the stable MIR API boundary.

  2. Added make stdlib-smir. A single command that builds stable-mir-json, creates a throwaway crate in a temp directory, runs -Zbuild-std through the driver, and collects the resulting smir.json artifacts. Against nightly-2024-11-29, this produces 21 artifacts (~100MB total), including std (44MB, 11,950 items), core (16MB), alloc, proc_macro, and all their transitive dependencies.

  3. Binary-searched for the most recent working nightly. This is where the compat layer's value became clear.

Nightly compatibility: what I found

Starting from the pinned nightly-2024-11-29, I walked forward to find how far the current codebase can go without changes:

Nightly Status Breaking change
2024-11-29 (pinned) builds
2024-12-14 builds (last working)
2024-12-15 1 error AggregateKind::CoroutineClosure added
2025-01-15 1 error same
2025-03-01 7 errors more stable MIR API evolution
2025-07-14 44 errors accumulated breakage
2026-03-07 can't find crate stable_mir renamed to rustc_public

A natural question: shouldn't the compat layer handle all of these? Turns out, no; and that's by design. The breaks fall into two categories:

Category 1: rustc internals (compat handles these). Crate renames (stable_mir to rustc_public, rustc_smir to rustc_public_bridge), function signature changes, struct field changes. These are one-line fixes in compat/mod.rs or compat/*.rs. The 13-month stress test (ADR-003) validated this.

Category 2: stable MIR public API evolution (compat intentionally does not handle these). New enum variants (AggregateKind::CoroutineClosure), changed parameter types (Rvalue::AddressOf), removed variants (StatementKind::Deinit). These are types that printer/ and mk_graph/ directly pattern-match on. Any consumer of stable MIR would face the same breakage; wrapping every enum in a compat shim would mean reimplementing the entire API surface, defeating the purpose.

What the compat layer tells us

The compat layer's value here isn't that it magically absorbs all changes; it's that it gives a clean diagnostic signal. When you bump the nightly and hit errors:

  • Errors in src/compat/ or src/driver.rs: rustc internals shifted. Fix them in-place; nothing else needs to change.
  • Errors in src/printer/ or src/mk_graph/: stable MIR's public contract evolved. These require real code changes (new match arms, updated call sites), and any stable MIR consumer would need the same fixes.

Without the compat layer, both categories would be tangled together across the entire codebase, and there'd be no way to tell which changes are mechanical (rename/re-export) vs. semantic (new language feature support).

What needs to happen to support newer nightlies

The first break (nightly-2024-12-15) is a single new enum variant: AggregateKind::CoroutineClosure. That's a straightforward match arm addition. Beyond that, the stable MIR API changes accumulate gradually (7 errors by March 2025, 44 by July 2025), and the crate rename lands around July 2025.

A reasonably cromulent plan:

  1. Handle CoroutineClosure and the other stable MIR API changes in printer/ and mk_graph/ (these are real semantic additions, not compat busywork)
  2. Add compat aliases for the stable_mir -> rustc_public rename (one-line change, already validated in the stress test)
  3. Bump the pinned nightly

Each step is independently testable: build, run integration tests, run make stdlib-smir, run make test-ui.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions