Rust 1.95 稳定版解读与生态新动向
一、Rust 1.95.0:三个值得关注的新特性
Rust 1.95.0 已于 2026 年 4 月 16 日正式发布。这个版本带来了几项对日常编码体验影响较大的改进。
1. cfg_select! ------ 编译时条件选择的官方方案
过去,如果你想在编译时根据不同的目标平台(如 unix、windows、32 位、64 位)执行不同的代码分支,最常用的方式是引入第三方 crate cfg-if。写法大概是这样的:
rust
cfg_if::cfg_if! {
if #[cfg(unix)] {
fn foo() { /* unix 特定实现 */ }
} else if #[cfg(target_pointer_width = "32")] {
fn foo() { /* 32 位实现 */ }
} else {
fn foo() { /* 兜底实现 */ }
}
}
Rust 1.95 把它变成了语言内置特性,不需要再引入任何依赖:
rust
cfg_select! {
unix => {
fn foo() { /* unix 特定实现 */ }
}
target_pointer_width = "32" => {
fn foo() { /* 32 位实现 */ }
}
_ => {
fn foo() { /* 兜底实现 */ }
}
}
为什么这很重要?
- 减少了一个几乎每个跨平台项目都会引入的依赖
- 编译器对内置宏的理解更充分,IDE 支持和错误提示更好
- 语法更简洁:
条件 => { 代码块 },一目了然
你甚至可以直接用它在表达式中做选择:
rust
let platform = cfg_select! {
windows => "windows",
target_os = "macos" => "macos",
_ => "unix-like",
};
2. if-let 守卫进入 match 表达式
Rust 1.88 引入了 let chains(if let 链式条件),现在这种能力扩展到了 match 表达式中。
看一个实际场景 :假设你有一个 Option<i32> 类型的值,需要先解包,再对一个可能失败的操作做判断:
rust
// Rust 1.95 之前:需要嵌套 if let
match value {
Some(x) => {
if let Ok(y) = compute(x) {
println!("{}, {}", x, y);
}
}
None => {}
}
// Rust 1.95:直接在 match arm 上加 if-let 守卫
match value {
Some(x) if let Ok(y) = compute(x) => {
// x 和 y 在这里都可用
println!("{}, {}", x, y);
}
_ => {}
}
实际意义 :这让你在 match 中可以同时做模式匹配和条件判断,减少嵌套层级,代码更加扁平。这在处理嵌套 Result<Option<T>> 等复杂类型时尤其有用。
注意 :
if-let守卫中的模式不会 参与match的穷尽性检查,这一点和普通if守卫一致。也就是说,编译器不会因为它而认为你覆盖了更多情况。
3. 一批标准库 API 稳定化
这个版本稳定了大量实用 API,挑几个值得关注的:
MaybeUninit 与 Cell 的数组转换
rust
// 现在可以直接在 [MaybeUninit<T>; N] 和 MaybeUninit<[T; N]> 之间转换
let arr: [MaybeUninit<u8>; 4] = /* ... */;
let unified: MaybeUninit<[u8; 4]> = arr.into();
这在做底层系统编程、FFI 交互时非常常见,以前需要 unsafe 手写转换,现在安全地完成。
集合类型的 _mut 方法
Vec、VecDeque、LinkedList 都新增了 push_mut、insert_mut 等方法。这些方法返回可变引用而非 Copy 值,适用于元素类型不实现 Copy 的场景:
rust
let mut v = vec![String::from("hello")];
let s: &mut String = v.push_mut(String::from("world"));
// 拿到刚插入元素的 &mut 引用,而不是一个被 Move 出去的值
Atomic*::update 和 try_update
所有原子类型新增了基于闭包的原子更新方法:
rust
use std::sync::atomic::AtomicI32;
let counter = AtomicI32::new(0);
// 原子地执行:读取 -> 修改 -> CAS 重试
counter.update(|old| old + 1);
这替代了手写 compare_exchange 循环的样板代码,简洁且不易出错。
二、docs.rs 构建策略变化:从 5 个目标减少到 1 个
变化时间:2026 年 5 月 1 日生效
docs.rs 是 Rust 生态的在线文档服务,几乎所有 crate 的文档都托管在上面。目前的行为是:如果一个 crate 没有显式声明构建目标,docs.rs 会为默认 5 个目标平台各构建一份文档。
从 5 月 1 日起 ,默认行为将改为只构建一个目标 (通常是 x86_64-unknown-linux-gnu)。
对你的影响:
| 你的情况 | 是否受影响 |
|---|---|
| 纯 Rust 代码,不区分平台 | 不受影响 |
crate 使用了 #[cfg(target_os = "...")] 等平台特定代码 |
需要在 Cargo.toml 中声明目标 |
| FFI crate,绑定多个平台 | 必须声明 |
如何适配 :在 Cargo.toml 中显式声明:
toml
[package.metadata.docs.rs]
targets = [
"x86_64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-pc-windows-msvc",
]
为什么这么做? 大多数 crate 在不同平台上的代码完全一样,多构建几份文档浪费的是 docs.rs 的服务器资源和你的构建时间。这个改变从 2020 年开始酝酿,如今终于落地。
三、WebAssembly 工具链:移除 --allow-undefined 历史遗留
这个变化对 Rust + WebAssembly 开发者来说是一个潜在破坏性更新。
背景
从 Rust 最早支持 WebAssembly 开始,链接器 wasm-ld 就一直带着 --allow-undefined 标志。这个标志的含义是:允许链接后的二进制中存在未定义的符号。
问题
这意味着如果你写错了函数名、忘了链接外部 C 库,编译器不会报错 ,而是默默把那个符号标记为从 "env" 导入。直到运行时你才会发现------而且错误信息很难追溯到源码。
举个例子:
rust
unsafe extern "C" {
fn mylibrary_init(); // 正确
}
// 如果你拼写错误:
unsafe extern "C" {
fn mylibraryinit(); // 编译不报错!运行时才崩
}
变化
Rust 正在移除这个标志,让 WebAssembly 目标和其他原生平台行为一致:未定义符号 = 编译错误。
你需要做什么
- 如果你的 WebAssembly 项目正确声明并链接了所有外部符号 → 不受影响
- 如果你依赖了
"env"隐式导入 → 需要修复代码,显式声明符号来源 - 如果你用了
wasm-bindgen等工具 → 一般不受影响,它们处理的是正确的符号导入
四、生态观察
结合 4 月的这些更新,我们可以看到 Rust 发展的几个趋势:
1. 语言本身在"补全"而非"颠覆"
cfg_select! 和 if-let 守卫都不是全新概念,而是把社区已经验证过好用、但需要第三方库或 workaround 的能力收归标准库。这说明 Rust 的演进策略越来越成熟------先让生态试水,再纳入语言。
2. 工具链在减少历史包袱
移除 WebAssembly 的 --allow-undefined 和 docs.rs 减少默认构建目标,都是在清理早期为了"能用就行"而留下的技术债。这些变化虽然可能短期带来适配成本,但长期来看让工具链行为更一致、更可预测。
3. 标准库持续"填坑"
Atomic*::update、MaybeUninit 数组转换这些 API 的稳定化,针对的都是实际开发中频繁出现的场景。Rust 团队明显在系统性地减少那些"明明很常用,却还要自己写 unsafe"的痛点。
五、升级建议
bash
# 升级到 1.95.0
rustup update stable
# 验证版本
rustc --version # 应显示 1.95.0
如果你维护 WebAssembly 项目,建议在升级后做一次完整的 cargo build --target wasm32-unknown-unknown 测试,确认没有未定义符号被静默放过。