Protecting the Rust standard library from accidental breakage

birdculture2 pts0 comments

-->

Protecting the Rust standard library from accidental breakage

Protecting the Rust standard library from accidental breakage

August 15, 2026

semver

rust

Accidental breakage can happen in any codebase. The Rust standard library isn't magically exempt from this — so it too now uses cargo-semver-checks to prevent accidental breakage. Here's why this took months of work by multiple Rustaceans, dozens of pull requests, and 15,000+ lines of code across the Rust repo, cargo-semver-checks, and its component libraries.

In September 2020, an unstable required method was added to a stable std trait.<br>The seemingly innocuous change broke async-std on nightly, and was promptly reverted.

In June 2021, a generic method was added to core's BuildHasher trait. The method accidentally did not have a where Self: Sized guard, so it made BuildHasher no longer dyn-safe. The problem was discovered during Rust 1.55-beta's crater run, and required a fix to avoid breaking stable Rust.

In July 2022, a soundness fix for iterators like ChunksMut was merged into core. The new implementation accidentally no longer implemented the Send and Sync auto-traits and needed to be patched to avoid breaking stable Rust.

In March 2026, tokio maintainers found that their test suite did not compile in Rust 1.94 on Windows. Another std trait had gained unstable methods, and the breakage was sufficiently painful that a fix was shipped in the Rust 1.94.1 point release.

I could go on. [Sidenote: There are two more instances I've found since 2020. My search was not exhaustive. Likely there are more.]

Humans simply cannot reliably catch accidental breakage. I reviewed each of the breakage-inducing PRs above, and I do not believe I could have spotted the problem on my own. Neither did the much more experienced authors and reviewers who originally participated in those PRs! Our best effort is not enough, so we turn to tooling.

cargo-semver-checks can catch all of these issues today. We have chosen to not let them happen again!

Stability, breakage, and stability breakage

The straightforward case: item stability

Partial stability makes everything harder

Plugging stability info into cargo-semver-checks

How we got here and what lies ahead

Thanks to Jakub Beránek (kobzol), the rustdoc team, the library and library contributors teams, the RustWeek 2026 and Rust All Hands organizers, and the many other Rustaceans who put their time, energy, and goodwill toward accomplishing this goal 🦀 cargo-semver-checks stands on the shoulders of giants.

Stability, breakage, and stability breakage

The Rust standard library uses stability as a mechanism to separate APIs usable in regular Rust releases from those that are experimental and only usable in nightly Rust on an opt-in basis.

As the name suggests, unstable APIs offer no stability or SemVer guarantees and may change at any time. Meanwhile, stable APIs behave exactly like the public API of any other Rust library.

To start, applying cargo-semver-checks to the standard library required understanding the difference, lest we frustrate maintainers by making CI complain about API breakage of explicitly-unstable APIs. [Sidenote: Of course, there's a difference between intended breakage of unstable APIs, and unintentional breakage of such APIs. We haven't built this yet so there's room for an even deeper integration here! But generally, breakage of unstable APIs should be reported as "here's what changed, please make sure you intended this" without blocking CI.]

There's another class of breakage too: de-stabilizing a previously-stable API. Sadly, this is also not merely a hypothetical case: this breakage flavor has precedent too. We wanted to catch this too — and we did.

Finally, items' name and existence can be stable but some of their facets, like const or a default value, may not be stable. cargo-semver-checks had to model this as well.

We needed to solve two sets of challenges: exposing stability information in rustdoc JSON so cargo-semver-checks can read it, and making stability fit into the cargo-semver-checks linting data model without needing to rewrite hundreds and hundreds of lints.

Let's discuss stability and rustdoc JSON first.

The straightforward case: item stability

Check out this example:

#[stable(feature = "example", since = "1.0.0")]<br>pub struct Example {<br>#[stable(feature = "example", since = "1.0.0")]<br>pub stable_field: u32,

#[unstable(feature = "example_unstable_field", issue = "none")]<br>pub unstable_field: u32,<br>As you can see, stable_field can be used on stable Rust, while unstable_field requires nightly Rust and an explicit opt-in with #![feature(example_unstable_field)]. [Sidenote: As a consequence, stable code cannot create a fresh Example from field expressions alone and must use .. in patterns, even though the struct isn't formally #[non_exhaustive]. Functional update syntax like Example { stable_field, ..existing } using an existing Example still works.]

Both removing...

rust breakage stability semver stable library

Related Articles