Protecting the Rust standard library from accidental breakage

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.

保护 Rust 标准库免受意外破坏 2026 年 8 月 15 日 | semver | rust

任何代码库都可能发生意外破坏。Rust 标准库也不例外——因此,它现在也开始使用 cargo-semver-checks 来防止意外破坏。这项工作耗费了多位 Rust 开发者数月的时间,涉及数十个拉取请求(PR),并在 Rust 仓库、cargo-semver-checks 及其组件库中贡献了超过 15,000 行代码。

In September 2020, an unstable required method was added to a stable std trait. 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.

2020 年 9 月,一个不稳定的必要方法被添加到了一个稳定的 std trait 中。这个看似无害的改动破坏了 nightly 版本上的 async-std,并被迅速回滚。2021 年 6 月,一个泛型方法被添加到了 coreBuildHasher trait 中。该方法意外地缺少了 where Self: Sized 约束,导致 BuildHasher 不再满足 dyn-safe(动态安全)。这个问题在 Rust 1.55-beta 的 crater 测试中被发现,并需要进行修复以避免破坏稳定的 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.]

2022 年 7 月,一个针对 ChunksMut 等迭代器的健全性修复被合并到 core 中。新的实现意外地不再实现 SendSync 自动 trait,因此需要打补丁以避免破坏稳定的 Rust。2026 年 3 月,tokio 的维护者发现他们的测试套件在 Windows 上的 Rust 1.94 中无法编译。另一个 std trait 增加了不稳定的方法,破坏程度非常严重,以至于 Rust 1.94.1 版本不得不发布修复程序。我还可以举出更多例子。[注:自 2020 年以来,我还发现了另外两个实例。我的搜索并不详尽,很可能还有更多。]

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!

人类根本无法可靠地捕捉到意外破坏。我审查了上述每一个导致破坏的 PR,我不认为我能凭一己之力发现这些问题。当初参与这些 PR 的、经验丰富得多的作者和审查者们也没能发现!仅靠我们尽力而为是不够的,所以我们转向了工具。cargo-semver-checks 现在可以捕捉到所有这些问题。我们决定不再让它们再次发生!

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.

稳定性、破坏与稳定性破坏

Rust 标准库使用“稳定性”作为一种机制,将常规 Rust 版本中可用的 API 与那些仅在 nightly Rust 中通过选择性加入(opt-in)使用的实验性 API 分离开来。顾名思义,不稳定的 API 不提供任何稳定性或 SemVer 保证,并可能随时更改。与此同时,稳定的 API 的行为与任何其他 Rust 库的公共 API 完全一致。

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

首先,将 cargo-semver-checks 应用于标准库需要理解这种差异,以免因 CI 对明确不稳定的 API 的破坏发出警告而让维护者感到沮丧。[注:当然,有意破坏不稳定 API 与无意破坏这些 API 是有区别的。我们尚未实现这一点,因此这里还有更深入集成的空间!但通常情况下,不稳定 API 的破坏应报告为“这是更改内容,请确保这是您的意图”,而不应阻塞 CI。]

The straightforward case: item stability

Check out this example:

#[stable(feature = "example", since = "1.0.0")]
pub struct Example {
    #[stable(feature = "example", since = "1.0.0")]
    pub stable_field: u32,
    #[unstable(feature = "example_unstable_field", issue = "none")]
    pub unstable_field: u32,
}

直观的情况:项(Item)稳定性

看看这个例子:

#[stable(feature = "example", since = "1.0.0")]
pub struct Example {
    #[stable(feature = "example", since = "1.0.0")]
    pub stable_field: u32,
    #[unstable(feature = "example_unstable_field", issue = "none")]
    pub unstable_field: u32,
}

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

如你所见,stable_field 可以在稳定版 Rust 上使用,而 unstable_field 需要 nightly 版 Rust 并通过 #![feature(example_unstable_field)] 显式启用。[注:因此,稳定代码不能仅通过字段表达式创建新的 Example,必须在模式中使用 ..,即使该结构体并未正式标记为 #[non_exhaustive]。使用现有 Example 的函数式更新语法(如 Example { stable_field, ..existing })仍然有效。]

Partial stability makes everything harder

Item stability isn’t the whole story. An item’s name and existence can be stable while only some of its capabilities are stable. Take const functions, for example:

#[stable(feature = "example", since = "1.0.0")]
#[rustc_const_unstable(feature = "example_const", issue = "none")]
pub const fn answer() -> u32 { 42 }

部分稳定性让一切变得更复杂

项的稳定性并不是全部。一个项的名称和存在性可能是稳定的,但其部分功能可能是不稳定的。以 const 函数为例:

#[stable(feature = "example", since = "1.0.0")]
#[rustc_const_unstable(feature = "example_const", issue = "none")]
pub const fn answer() -> u32 { 42 }

Outside of const contexts, answer() can be called normally on stable Rust. But calling it inside const is unstable, requiring nightly Rust and an explicit #![feature(example_const)] opt-in. Removing const from this function therefore isn’t a breaking change from the perspective…

在 const 上下文之外,answer() 可以在稳定版 Rust 上正常调用。但在 const 内部调用它是不稳定的,需要 nightly 版 Rust 并显式启用 #![feature(example_const)]。因此,从这个角度来看,从该函数中移除 const 并不是一个破坏性更改……