Rust 1.100.0 发布公告
Rust 团队很高兴地宣布 Rust 新版本 1.100.0。Rust 是一门让每个人都能构建可靠且高效软件的编程语言。
如果你之前通过 rustup 安装过 Rust,可以用以下命令获取 1.100.0:
$ rustup update stable
如果你还没有安装,可以从我们网站上对应的页面获取 rustup,并查看 1.100.0 的详细发布说明。
如果你想通过测试未来版本来帮助我们,可以考虑在本地更新到 beta 通道(rustup default beta)或 nightly 通道(rustup default nightly)。请报告你可能遇到的任何 bug!
1.100.0 稳定版有哪些内容
- Allocator API
- DropGuard API
- 召唤零大小类型
- Cargo 中报告未使用的依赖
- 减少 Cargo 缓存冲突
- Cargo min-publish-age
- 稳定化的 API
Never 类型
在历经 10 年和 5 次失败的稳定化尝试之后,never 类型终于稳定了!这也完成了这项工作的项目目标。
never 类型在代码中写作 !,是规范的空类型(uninhabited type)。它表示发散计算——即永远不会解析为任何值的计算——的类型,其后的任何代码都不可达。终止控制流的表达式,例如 return、break 和 std::process::exit,其类型也是 !。
由于拥有 never 类型的值是不可能的,Rust 允许 never-to-any 强制转换,从而可以把 ! 转换为任何其他类型(这正是它与其他任何空类型的不同之处)。例如,可以用它来让 match 或 if 的某个分支不返回任何值:
fn mrrrow(option: Option<u32>) {
let value = match option {
// `x` 的类型是 `u32`
Some(x) => x,
// `return` 的类型是 `!`,随后被强制转换为 `u32`,
// 使得该 `match` 能通过类型检查。
None => return,
};
// ...
}
尽管 never 类型此前已经可以这样使用和观察到,甚至可以在函数返回类型中命名它(-> !),本次发布允许在任何地方命名 never 类型:
fn infallible_unwrap<T>(res: Result<T, !>) -> T {
// ^^ 此前不允许写 `Result<_, !>`
let Ok(v) = res;
v
}
此外,本次发布将 Infallible 改为 never 类型的别名。这使得所有使用 Infallible 的旧 API 都能与 ! 兼容。这自 Infallible 引入以来就是计划之中的,也正是 never 类型稳定化如此艰难的原因之一(waffle 曾就此稳定化过程中的一些麻烦做过演讲)。
遗憾的是,为了能够改动 Infallible,我们不得不引入一个破坏性变更。本次发布将 2024 之前版本(即 2015、2018 和 2021 版)中的“never 类型回退”从 () 改为 !,与 2024 版中已经完成的做法类似。这在很大程度上应该不易察觉,但在某些罕见情况下可能影响代码(例如忽略一个返回泛型参数的函数的结果时)。请注意,这一变更也可能让编译器把更多代码识别为不可达。
我们尽可能减小了这一破坏性变更的影响:提前很久就提供了未来兼容性警告,并为因此被破坏的公开 crate 发布了修复。如果某个依赖因 never 类型的问题而失败,可以考虑更新它,因为可能已经为它发布了修复。
Allocator API
本次发布稳定化了 allocator_api 的核心部分,使库作者能够定义可与某些标准库类型一起使用的自定义分配器——目前是 Vec 和 Box。这些通过新稳定化的 Allocator trait 启用。
这是 Allocators 1.0 项目目标的基础。仍有许多工作要做,包括扩展可用于自定义分配器的 API 范围,以及使其在更多集合和指针类型上的使用稳定下来;不过既然核心 trait 和设计已经确定,预计很快会有更多内容被稳定下来!
DropGuard:在每一条退出路径上运行闭包
Rust 中的 Drop trait 让变量在离开作用域时运行代码成为可能,无论我们是正常返回、通过 ? 返回还是 panic。Drop 是类型的一部分,这意味着在 Rust 中你永远不需要手动关闭 socket。
然而,当你只是想在函数退出时无条件运行一些代码时,实现 Drop 可能显得有些不便。这就是我们引入 core::mem::DropGuard 的原因:一个在被 drop 时运行闭包的新的辅助类型。
DropGuard 接受两个参数:一个变量和一个闭包。它实现了 Deref 和 DerefMut,因此你可以像平常一样继续使用该变量。但当它被 drop 时,它会运行闭包并消耗该变量。
DropGuard 还实现了 dismiss 方法,可以从 guard 中取回变量而不运行析构函数。这使得快速实现带回滚的全有或全无式事务类 API 成为可能。下面是一个实现解析器的示例,它使用 guard 在失败时回滚变更:
use std::mem::DropGuard;
use std::num::ParseIntError;
/// Attempt to parse all `strings` and push them into `nums`.
/// If parsing any of them fails, `nums` remains unchanged.
fn parse_all(nums: &mut Vec<u32>, strings: &[&str]) -> Result<(), ParseIntError> {
let len = nums.len();
let mut nums = DropGuard::new(nums, move |nums| {
nums.truncate(len); // ← 1. 失败时重置这个 vec
});
for s in strings {
nums.push(s.parse()?); // ← 2. 如果 `?` 返回错误,则回滚
}
DropGuard::dismiss(nums); // ← 3. 解析成功,解除 guard
Ok(())
}
凭空构造零大小类型
由于 Rust 的不安全代码指南仍在确定之中,有一点变得很清楚:明确以不安全方式创建零大小类型究竟意味着什么,以及我们应该推荐人们如何去做,将会非常有用。
这个问题在 Rust 1.100 中由一个函数回答:std::mem::conjure_zst。
不必再使用 transmute ()、MaybeUninit::uninit().assume_init() 或各种其他咒语,当你确信这样做是安全的时,这就是创建零大小类型值的专用方式。如果一个 inhabited 类型没有大小,我们就确切知道它必须是哪个值:什么都没有。
当然,API 常常依赖这类类型无法被构造来保证安全,这就是这个函数是 unsafe 的原因。例如,PyO3 的 Python<'py> 是一个零大小标记令牌,表示持有 Python GIL(全局解释器锁)。Python 的许多 API 要求持有该锁才是安全的,但凭空变出一个令牌会让安全的 PyO3 调用得以违反这一前提条件。
在 Cargo 中报告未使用的依赖
构建不再使用或仅在特定平台需要的依赖会拖慢构建速度。Rustc 为此提供了 unused_crate_dependencies lint,但在与 Cargo 一起使用时它会出现误报,因为依赖可能在多个构建目标之间共享,例如 dependencies.tracing-subscriber 在一个包中对每个 bin 都可用,而其中可能只有部分会用到它。
Cargo 现在会向用户报告未使用的依赖,例如:
warning: unused dependency `tracing-subscriber`
--> Cargo.toml:23:1
|
23 | tracing-subscriber.workspace = true
| ^^^^^^^^^^^^^^^^^^
|
= note: `cargo::unused_dependencies` is set to `warn` by default
help: consider removing the dependency on `tracing-subscriber`
此警告的一个限制是,只有当构建可能使用依赖表中条目的每个构建目标时,它才会出现。例如,运行 cargo check --bin foo 不会显示未使用的 [dependencies] 的警告,因为可能存在另一个使用它们的 bin。未使用的 dev-dependencies 不会显示警告,因为无法确保每个开发构建目标都被构建。
此警告是作为 Cargo 新 lint 系统的一部分引入的,该系统由 Cargo.toml 中的 [lints.cargo] 表控制,从而完成了这个项目目标。另请参阅已引入的其他 lint。
减少 Cargo 缓存冲突
Cargo 会在 build-dir 中构建所有库和二进制文件,然后将最终产物提升到 target-dir。目前,它们共享一个共同的父目录。不同的构建配置(例如 --features)可能导致 build-dir 中出现不同副本的库和二进制文件。这些副本通过构建配置哈希保持唯一,但由于平台限制,该哈希并不总是被应用。这可能导致额外的重建、遗漏重建、对使用了哪些二进制文件或库感到困惑等。在跨 git worktree 共享 build-dir 时,这些问题会加剧。
现在,build-dir 通过围绕构建步骤而非文件类型来组织目录,并将哈希从文件名移到父目录,从而一致地将构建配置哈希应用于所有内容。
这项工作始于 2025 年的构建目录布局项目目标,其稳定化是迈向新项目目标的一个里程碑,为未来的改进铺平了道路,例如缓存垃圾回收、跨工作区缓存去重、跨工作区缓存以及细粒度锁定。
我们之前发布了一项测试征集,针对现在正在生效的更改。虽然这是一项内部更改,但它可能会破坏那些针对缺失或不完整功能采用了变通方法的构建过程和测试。在某些情况下,不再需要该变通方法。有关可能的解决方案以及添加任何新用例,请参阅 cargo#17287。
Cargo min-publish-age,又称依赖冷却期
恶意版本的包通常会在发布后的数小时或数天内被自动化扫描器或社区发现。Rust 1.100 稳定了 Cargo 的 min-publish-age 感知依赖解析,让你可以等待该窗口过去,然后再选择新发布的版本。这种做法也称为依赖冷却期。
你可以通过 .cargo/config.toml 选择启用:
[registry]
global-min-publish-age = "3 days"
选择版本时,Cargo 现在会跳过任何比该时间更近发布的版本,并告诉你它拦下了什么:
$ cargo update
Updating crates.io index
Locking 2 packages to highest compatible version as of min-publish-age
Adding cc v1.11.0 (available: v1.18.0, published 3 hours ago)
Adding clap 4.6.7 (available: 4.6.9, published 2 days ago)
当需要立即使用新版本时,你可以为单次更新覆盖该检查:
$ CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow cargo update clap --precise 4.6.9
Updating crates.io index
Updating clap 4.6.7 -> clap 4.6.9 (published 2 days ago, minimum age 3 days)
较新的版本将记录在 Cargo.lock 中,并从那时起保留,直到之后的 cargo update 重新解析它。
有几点需要记住:
- 这取决于注册表记录每个版本的发布时间。索引中没有 pubtime 字段的版本永远不会被拦下。
- cargo install 会忽略最小发布年龄,无论是安装的 crate 还是其依赖项。如有需要,可使用 cargo install [email protected] 固定版本。
- 冷却期可降低某些供应链攻击的风险,但其本身并非完整的防御措施。请仍像往常一样审查依赖项更新。
计划在 crates.io 供应链加固项目目标下开展进一步保护生态的工作,包括隔离支持和自动检测可疑发布。
稳定化的 API
- impl PartialEq<Cow<'_, [U]>> for Vec<T, A>
- impl PartialEq<Cow<'_, [U]>> for &[T]
- impl PartialEq<Cow<'_, [U]>> for &mut [T]
- std::alloc::Allocator
- std::alloc::Global
- std::alloc::System
- impl Allocator for Global
- impl Allocator for System
- impl<A: Allocator> Allocator for &A
- impl<T: Allocator, A: Allocator> Allocator for Box<T, A>
- impl<A: Allocator> Allocator for &mut A
- impl<T: Allocator, A: Allocator> Allocator for Arc<T, A>
- impl<T: Allocator, A: Allocator> Allocator for Rc<T, A>
- Box::from_raw_in
- Box::from_non_null_in
- Box::into_raw_with_allocator
- Box::into_non_null_with_allocator
- Box::allocator
- Vec::with_capacity_in
- Vec::from_raw_parts_in
- Vec::from_parts_in
- Vec::into_raw_parts_with_allocator
- Vec::into_parts_with_allocator
- Vec::allocator
- Child::main_thread_handle
- impl Default for std::sync::Once
- bool::toggle
- impl AsRef<T> for !
- impl AsMut<T> for !
- core::mem::DropGuard
- core::mem::conjure_zst
- UnsafeCell::replace
- UnsafeCell::as_ref_unchecked
- UnsafeCell::as_mut_unchecked
- str::trim_prefix
- str::trim_suffix
- [T]::trim_prefix
- [T]::trim_suffix
- Path::trim_prefix
- Vec::from_fn
- os::windows::process::CommandExt::show_window
其他变更
查看 Rust、Cargo 和 Clippy 中所有变更。
1.100.0 的贡献者
许多人齐心协力创造了 Rust 1.100.0。没有你们所有人,我们无法做到。谢谢!