2026 年 8 月 24 日,rust-lang/rust 仓库的 PR #155499 完成合并,标题只有三个词:stabilize never type。这个 PR 把 Rust 的 never type(写成感叹号 !)正式推进稳定通道,同时关闭了两条跟踪了十年的 issue:#35121(2016 年 7 月开立,「promoting ! to a type」)和 #148922(never type fallback 变更)。改动规模 +1,798/−3,635 行,横跨 100 个文件,由社区开发者 WaffleLapkin 主导,经历了 80 条评论、29 轮 review 和两轮 crater 全生态编译实验。
对大多数 Rust 使用者来说,日常代码不会因为这个变化多打一个字。但它背后是一连串有意思的问题:一个「永远不会产生值」的类型有什么用?它为什么需要动整个生态的编译行为,Rust 团队又如何在不炸掉 crates.io 的前提下落地一次教科书级别的破坏性变更,是这篇文章要展开的两个问题。
never type 是什么:类型系统里的「死路标」
Rust 的每个类型都描述「这个位置的值可能是什么」:u8 是 0 到 255 的整数,String 是一段字符串。never type ! 描述的是一种极限情况:这个位置永远不会有值出现。
它最常见的出没地点有两类。第一类是不会返回的函数。一个函数可以返回 Result<T, E>、返回 Option<T>,也可以什么都不返回(()),但如果它直接让程序崩溃(panic!)、陷入死循环(loop {})或者退出进程,它的返回类型就该是 !:不是「没有返回值」,是「根本走不到返回那一步」。
第二类是必然走不到的分支。标准库里的 FromStr trait 定义了「从字符串解析出值」的接口:
trait FromStr: Sized {
type Err;
fn from_str(s: &str) -> Result<Self, Self::Err>;
}大多数实现都会失败:把 "foo" 解析成整数会得到 ParseIntError。但有些类型的解析永远不会失败,比如把任意字符串读成字节串(ByteString)。这种实现可以把 Err 直接设成 !:
impl FromStr for ByteString {
type Err = !;
fn from_str(s: &str) -> Result<Self, !> { ... }
}Result<Self, !> 的 Err 分支永远不会被构造出来。编译器知道这一点之后,会直接把处理错误分支的代码优化掉。在 never type 稳定之前,标准库里有个替代品叫 Infallible(一个空枚举),语义相同但没有编译器特殊支持:用它写的代码类型检查能过,但优化器不总能清掉死代码。stabilization PR 里明确写了第三条改动:Infallible 变成 ! 的类型别名,两套写法从此合一。

never type 还有一个数学上的独特性质:它可以隐式转换成任何其他类型。这听起来危险,其实安全——正因为 ! 位置永远不会产生值,编译器可以放心地把「不可能到达的代码」当成死代码抹掉。这是类型系统驱动的死代码消除,也是它比 Infallible 更受青睐的原因。
卡了十年的真正原因:never type fallback
一个「永远不产生值」的类型要进入稳定版 Rust,难点不在它自身,而在它和类型推断的相互作用上。
Rust 的 if、loop 都是表达式,可以有值。那么一个死循环的「值」是什么类型?编译器需要一个答案才能统一处理类型推断。Rust 的方案是引入一条兜底规则:当类型推断走完所有正常流程后仍有歧义,就指定一个「fallback 类型」填进去。
在 2024 edition 之前,这个 fallback 是 ()(unit 类型)。2024 edition 把它改成了 ! 本身。两种选择都合法,但推断结果不同,某些代码只在其中一种规则下能编译。这是不折不扣的破坏性变更,而 stabilization PR 做的事情比这更激进:把新规则回移植到所有旧 edition。
冒这个险的动机在于,旧规则留着一个坑:! 到具体类型的隐式转换依赖 fallback 的方向,fallback 留在 () 时,编译器会时不时给出违反直觉的推断结果,而且标准库还要维护 ! 与 Infallible 两套并行的语义。fallback 统一到 ! 之后,隐式转换只在「类型对得上」的时候发生,语言规则变简单了。Rust 维护者判断,这个简化值得用一次受控的破坏来换取。
crater:用整个 crates.io 做回归测试
破坏性变更的代价必须先量化。Rust 社区的工具是 crater:把 crates.io 上的公开库全部拉下来,用包含变更的编译器逐一编译,统计多少个挂掉。
这次 crater 实验的结果:9,024 个参与编译的 crate 中,3,277 个回归。数字看着吓人,拆开看完全是另一回事。
直接被变更打断的 crate 只有 7 个,其余 3,270 个全是依赖问题:它们依赖某个老版本的库,而那些库的新版本早就修好了。这是因为 Rust 从 2024 年起就在编译器里埋了 future compatibility warning(dependency_on_unit_never_type_fallback lint),任何触发旧 fallback 行为的代码都收到过将近一年的警告,主流库在那段时间里陆续发布了修复版本。
crates.io 上绝大多数库已经修好,剩下的硬骨头是维护者已经不发布补丁版本的老库。WaffleLapkin 的做法是直接找上门去:逐个联系库的维护者,请求为老版本发布 backport 补丁。结果记录在 2026 年 8 月 14 日的状态更新里:
- 发布了 backport 的:
redis(约 527 个 crater 报错,维护者没空,WaffleLapkin 自己写了补丁、时任维护者负责发布)、ashpd(约 380 个)、trie-db(约 291 个)、wl-clipboard-rs(约 213 个)、picky-asn1-x509(约 45 个) - 拒绝 backport 的:
sqlx(约 839 个报错,维护者认为老版本已停止维护,用户应升级)、solana-client(约 340 个) - 意向待办的:
bluer(约 97 个)
语言团队最终拍板的逻辑也在这里:直接受影响的代码极少(7 个 crate),修复方式简单(升级依赖,或在调用处显式标注类型,比如把 x()? 写成 x::<()>()?),警告已发出近一年,继续等待的边际收益趋近于零。破坏被压缩到了可以逐个点名、逐个处理的地步。
这种操作方式本身就是内容:先让警告在生态里发酵两年,再用 crater 拿到精确到 crate 的破坏清单,然后一家一家上门把破坏面收窄到个位数,最后才按下合并键。Rust 承诺向后兼容,兑现承诺的方式包括「在充分沟通的前提下,有计划地打破一次」。
对普通开发者意味着什么
如果你在维护一个 Rust 项目,1.99 发布后大概率什么都不用做。编译失败的三种情况都有明确出路:
- 依赖太老:升级到包含修复的新版本,绝大多数库在 2024 到 2026 年间已陆续修复。
- 泛型函数推断歧义:典型的报错模式长这样——
fn x<T: Default>() -> Result<T, ()>被x()?直接调用。旧规则推断T = (),新规则推断T = !,而!没有实现Default,编译失败。修法是在调用处显式写x::<()>()?,或者用模式绑定() = x()?;。 - 完全不想动:留在 1.98 上继续编译,Rust 不强制升级。
长期收益在两端。一方面,Infallible 与 ! 合并后,新学语言的人少掉一个「为什么有两个永不类型」的困惑;标准库文档里那句「当 ! 稳定后,我们计划把 Infallible 变成它的别名」在挂了多年之后可以兑现了。另一方面,FromStr、TryFrom 这类 trait 的永不失败实现可以写出零开销的泛型代码,编译器有依据删掉全部错误处理路径。

这次变更的完整记录都在阳光下:PR #155499 的讨论区里有 crater 报告分析、给语言团队的技术说明、与每个库维护者的协商过程;waffle 此前在 RustWeek 2026 上做过名为《When is never?》的专场演讲。一个拖了十年的语言设计问题,最后以 7 个直接破坏、约 3,270 个依赖链破坏、5 个库 backport、2 个库拒绝 backport 的精确账目收尾。对正在考虑「怎么给一门语言收尾一个历史遗留特性」的人来说,这份账单本身就是参考答案。
参考来源
- LWN: Stabilizing Rust's never type
- rust-lang/rust PR #155499: stabilize never type(含 crater 分析与 backport 状态更新)
- rust-lang/rust issue #35121: Tracking issue for promoting
!to a type (RFC 1216) - rust-lang/rust issue #148922: Tracking Issue for never type fallback change
- rust-lang/rust issue #155924: Make
Infallible = ! - Waffle, RustWeek 2026: When is never?