Rust 团队很高兴地宣布 Rust 的新版本 1.51.0。Rust 是一种赋能每个人构建可靠且高效软件的编程语言。
如果您之前使用 rustup 安装了 Rust,获取 Rust 1.51.0 就像这样简单:
$ rustup update stable
如果您还没有安装,可以从我们网站上的相应页面获取 rustup,并在 GitHub 上查看 1.51.0 的详细发布说明。
1.51.0 stable 版本有什么新内容
此版本是 Rust 语言和 Cargo 相当长一段时间以来最重要的更新之一,它稳定了常量泛型 (const generics) 的 MVP (最小可用产品) 和 Cargo 的新特性解析器。让我们深入了解一下!
常量泛型 (Const Generics) MVP
在此版本之前,Rust 允许您的类型通过生命周期或类型进行参数化。例如,如果我们想要一个对数组元素类型泛型的 struct,我们可以这样写:
如果然后我们使用 FixedArray<u8>,编译器会创建一个 FixedArray 的单态版本,看起来像这样:
这是一个强大的特性,它允许您编写可重用且没有运行时开销的代码。然而,在此版本之前,还无法轻松地对这些类型的 值 进行泛型。这在数组中最为明显,数组的类型定义 ([T; N]) 中包含其长度,而之前您无法对其进行泛型。现在有了 1.51.0,您可以编写对任何整数、bool 或 char 类型的值进行泛型的代码!(使用 struct 或 enum 值仍然不稳定。)
这项更改现在允许我们拥有一个自己的数组 struct,它既对类型 也 对长度进行泛型。让我们来看一个定义示例,以及如何使用它。
现在,如果然后我们使用 Array<u8, 32>,编译器会创建一个 Array 的单态版本,看起来像这样:
常量泛型为库设计者提供了创建新的、强大的编译时安全 API 的重要新工具。如果您想了解更多关于常量泛型的信息,也可以查看“Const Generics MVP 进入 Beta”这篇博文,以获取更多关于此特性及其当前限制的信息。我们迫不及待地想看到您会创造出什么样的新库和 API!
array::IntoIter 稳定化
作为常量泛型稳定化的一部分,我们还稳定了一个使用它的新 API:std::array::IntoIter。IntoIter 允许您创建一个对任何数组进行值迭代的迭代器。以前,没有方便的方法来迭代数组的自有值,只能迭代它们的引用。
请注意,这被添加为一个单独的方法,而不是数组上的 .into_iter(),因为后者目前会引入一些破坏性变化;目前 .into_iter() 指的是切片的引用迭代器。我们正在探索未来使其更加符合人体工程学 (ergonomic) 的方法。
Cargo 的新特性解析器
依赖管理是一个棘手的问题,其中最困难的部分之一就是当一个依赖项被两个不同的包依赖时,如何选择使用它的哪个 版本。这不仅包括版本号,还包括为该包启用或未启用哪些特性。Cargo 的默认行为是在依赖关系图中多次引用同一个包时,合并其特性。
例如,假设您有一个名为 foo 的依赖项,包含特性 A 和 B,它被包 bar 和 baz 使用,但 bar 依赖于 foo+A,而 baz 依赖于 foo+B。Cargo 将合并这两个特性,并将 foo 编译为 foo+AB。这样做的好处是您只需要编译 foo 一次,然后 bar 和 baz 都可以重用它。
然而,这也带来了一个缺点。如果构建依赖项中启用的特性与您正在构建的目标不兼容怎么办?
生态系统中一个常见的例子是许多 #![no_std] crate 中包含的可选 std 特性,它允许 crate 在 std 可用时提供额外功能。现在想象一下,您想在您的 #![no_std] 二进制文件中使用 foo 的 #![no_std] 版本,并在构建时在您的 build.rs 中使用 foo。如果您的构建时依赖项依赖于 foo+std,那么您的二进制文件现在也依赖于 foo+std,这意味着它将无法编译,因为您的目标平台不可用 std。
这是 Cargo 中一个长期存在的问题,在此版本中,您的 Cargo.toml 中新增了一个 resolver 选项,您可以设置 resolver="2" 来告诉 Cargo 尝试一种新的特性解析方法。您可以查看 RFC 2957 以获取行为的详细描述,其概括如下。
- 开发依赖 (Dev dependencies) — 当一个包作为普通依赖和开发依赖共享时,只有在当前构建包含开发依赖时,才会启用开发依赖的特性。
- 主机依赖 (Host Dependencies) — 当一个包作为普通依赖、构建依赖或 proc-macro 共享时,普通依赖的特性与构建依赖或 proc-macro 的特性保持独立。
- 目标依赖 (Target dependencies) — 当一个包在构建图谱中多次出现,并且其中一个实例是特定于目标的依赖时,则只有在当前正在构建该目标时,才会启用该特定于目标的依赖的特性。
虽然这可能导致某些 crate 被多次编译,但这在使用 Cargo 的特性时应提供更直观的开发体验。如果您想了解更多信息,还可以阅读 Cargo Book 中的“特性解析器”部分。我们要感谢 Cargo 团队和所有参与设计和实现新解析器的人付出的辛勤工作!
[]
= "2"
# Or if you're using a workspace
[]
= "2"
拆分调试信息
虽然在发布时通常不突出,但 Rust 团队一直在努力改进 Rust 的编译时间,而此版本标志着 Rust 在 macOS 上一项长期以来最大的改进。调试信息将二进制代码映射回源代码,以便程序在运行时提供更多关于出错原因的信息。在 macOS 中,调试信息以前使用名为 dsymutil 的工具收集到一个单独的 .dSYM 文件夹中,这可能需要一些时间并占用相当大的磁盘空间。
将所有调试信息收集到此目录有助于在运行时查找它,尤其是在二进制文件被移动时。然而,它的缺点是即使您对程序进行了微小更改,dsymutil 仍然需要遍历整个最终二进制文件以生成最终的 .dSYM 文件夹。这有时会大大增加构建时间,特别是对于大型项目,因为所有依赖项都会被重新收集,但这以前是必要步骤,因为没有它,Rust 的标准库不知道如何在 macOS 上加载调试信息。
最近,Rust 回溯 (backtraces) 改为使用支持无需运行 dsymutil 即可加载调试信息的不同后端,并且我们已经稳定了跳过 dsymutil 运行的支持。这可以显著加快包含调试信息的构建速度,并显著减少占用的磁盘空间。我们尚未进行广泛的基准测试,但已经看到很多报告称在 macOS 上使用此行为后构建速度快了很多。
您可以通过在运行 rustc 时设置 -Csplit-debuginfo=unpacked 标志,或在 Cargo 的 [profile] 中将 split-debuginfo 选项设置为 unpacked 来启用此新行为。“unpacked”选项指示 rustc 将 .o 目标文件留在构建输出目录中而不是删除它们,并跳过运行 dsymutil 的步骤。Rust 的回溯支持足够智能,知道如何查找这些 .o 文件。像 lldb 这样的工具也知道如何做到这一点。只要您不需要在保留调试信息的同时将二进制文件移动到其他位置,此功能应该就可以工作。
[]
= "unpacked"
稳定化的 API
总的来说,此版本稳定了 slice 和 Peekable 等多种类型的 18 个新方法。一个值得注意的补充是 ptr::addr_of! 和 ptr::addr_of_mut! 的稳定化,它们允许您创建指向未对齐字段的裸指针。以前这不可能实现,因为 Rust 要求 & 和 &mut 是对齐的并指向已初始化的数据,而 &addr as *const _ 会导致未定义行为,因为 &addr 需要对齐。现在这两个宏让您可以安全地创建未对齐的指针。
use ptr;
let packed = Packed ;
// `&packed.f2` would create an unaligned reference, and thus be Undefined Behavior!
let raw_f2 = addr_of!;
assert_eq!;
以下方法已稳定化:
Arc::decrement_strong_countArc::increment_strong_countOnce::call_once_forcePeekable::next_if_eqPeekable::next_ifSeek::stream_positionarray::IntoIterpanic::panic_anyptr::addr_of!ptr::addr_of_mut!slice::fill_withslice::split_inclusive_mutslice::split_inclusiveslice::strip_prefixslice::strip_suffixstr::split_inclusivesync::OnceStatetask::Wake
其他变更
Rust 1.51.0 版本还有其他变更:查看 Rust、Cargo 和 Clippy 中的变更。
1.51.0 的贡献者
许多人共同努力创建了 Rust 1.51.0。没有你们,我们不可能做到。感谢!