Rust 团队很高兴地宣布 Rust 的新版本 1.51.0。Rust 是一种编程语言,它赋予每个人构建可靠且高效软件的能力。
如果您之前已通过 rustup 安装了 Rust,获取 Rust 1.51.0 就像
$ rustup update stable
如果您还没有,您可以从我们网站上的相应页面 获取 rustup
,并查看 GitHub 上的 1.51.0 的详细发布说明。
1.51.0 稳定版中的新功能
此版本代表 Rust 语言和 Cargo 的一项重大更新,稳定了 MVP 的常量泛型和 Cargo 的新特性解析器。让我们深入了解一下!
常量泛型 MVP
在此版本之前,Rust 允许您将类型参数化为生命周期或类型。例如,如果我们想要一个对数组元素类型进行泛化的 struct
,我们可以编写以下代码
struct FixedArray<T> {
// ^^^ Type generic definition
list: [T; 32]
// ^ Where we're using it.
}
如果我们随后使用 FixedArray<u8>
,编译器将生成一个看起来像 FixedArray
的单态版本
struct FixedArray<u8> {
list: [u8; 32]
}
这是一个强大的功能,它允许您编写可重用代码,并且没有运行时开销。但是,在此版本之前,无法轻松地对这些类型的值进行泛化。这在数组中最为明显,数组在其类型定义 ([T; N]
) 中包含其长度,以前您无法对其进行泛化。现在,使用 1.51.0,您可以编写对任何整数、bool
或 char
类型的值的泛型代码!(使用 struct
或 enum
值仍然不稳定。)
此更改现在允许我们拥有自己的数组结构,该结构对其类型和其长度进行泛化。让我们看一个示例定义,以及如何使用它。
struct Array<T, const LENGTH: usize> {
// ^^^^^^^^^^^^^^^^^^^ Const generic definition.
list: [T; LENGTH]
// ^^^^^^ We use it here.
}
现在,如果我们随后使用 Array<u8, 32>
,编译器将生成一个看起来像 Array
的单态版本
struct Array<u8, 32> {
list: [u8; 32]
}
常量泛型为库设计人员添加了一个重要的新工具,用于创建新的、强大的编译时安全的 API。如果您想了解有关常量泛型的更多信息,您还可以查看 "常量泛型 MVP 进入 Beta 版" 博客文章,以获取有关该功能及其当前限制的更多信息。我们迫不及待地想看看您将创建哪些新的库和 API!
array::IntoIter
稳定化
作为常量泛型稳定化的一部分,我们还稳定了使用它的新 API,std::array::IntoIter
。IntoIter
允许您为任何数组创建按值迭代器。以前,没有方便的方法来迭代数组的拥有值,只能迭代对它们的引用。
fn main() {
let array = [1, 2, 3, 4, 5];
// Previously
for item in array.iter().copied() {
println!("{}", item);
}
// Now
for item in std::array::IntoIter::new(array) {
println!("{}", item);
}
}
请注意,这是作为单独的方法添加的,而不是数组上的 .into_iter()
,因为这目前会导致一些中断;目前 .into_iter()
指的是按引用迭代切片。我们正在探索在将来使它更符合人体工程学的方法。
Cargo 的新特性解析器
依赖关系管理是一个难题,其中最难的部分之一就是当两个不同的包依赖同一个包时,选择使用哪个版本的依赖关系。这不仅包括其版本号,还包括为该包启用或禁用的功能。Cargo 的默认行为是在依赖关系图中多次引用同一个包时,合并该包的功能。
例如,假设您有一个名为 foo
的依赖关系,它具有功能 A 和 B,它被包 bar
和 baz
使用,但 bar
依赖于 foo+A
,而 baz
依赖于 foo+B
。Cargo 将合并这两个功能,并将 foo
编译为 foo+AB
。这样做的好处是您只需要编译 foo
一次,然后就可以将其重复用于 bar
和 baz
。
但是,这也存在缺点。如果在构建依赖关系中启用的功能与您要构建的目标不兼容怎么办?
生态系统中一个常见的例子是许多 #![no_std]
箱子中包含的可选 std
功能,该功能允许箱子在 std
可用时提供额外的功能。现在想象一下,您想在您的 #![no_std]
二进制文件中使用 #![no_std]
版本的 foo
,并在构建时在您的 build.rs
中使用 foo
。如果您的构建时依赖关系依赖于 foo+std
,那么您的二进制文件现在也依赖于 foo+std
,这意味着它将不再编译,因为 std
不适用于您的目标平台。
这在 cargo 中一直是一个长期存在的问题,在这个版本中,您的 Cargo.toml
中有一个新的 resolver
选项,您可以在其中设置 resolver="2"
以告诉 cargo 尝试一种新的解决功能的方法。您可以查看 RFC 2957,以详细了解该行为,该行为可以概括如下。
- 开发依赖关系 — 当一个包作为普通依赖关系和开发依赖关系共享时,只有在当前构建包含开发依赖关系时才会启用开发依赖关系功能。
- 主机依赖关系 — 当一个包作为普通依赖关系和构建依赖关系或 proc-macro 共享时,普通依赖关系的功能与构建依赖关系或 proc-macro 保持独立。
- 目标依赖关系 — 当一个包在构建图中多次出现,并且其中一个实例是特定于目标的依赖关系时,只有在当前构建目标时才会启用特定于目标的依赖关系的功能。
虽然这会导致一些箱子编译多次,但这应该在使用 cargo 功能时提供更直观的开发体验。如果您想了解更多信息,您还可以阅读 Cargo 手册中的 "特性解析器" 部分,以获取更多信息。我们要感谢 cargo 团队和所有参与设计和实现新解析器的人员!
[package]
resolver = "2"
# Or if you're using a workspace
[workspace]
resolver = "2"
拆分调试信息
虽然在发布中很少被强调,但 Rust 团队一直在不断努力改进 Rust 的编译时间,而此版本标志着 Rust 在 macOS 上很长时间以来最大的改进之一。调试信息将二进制代码映射回您的源代码,以便程序可以在运行时为您提供有关错误原因的更多信息。在 macOS 中,调试信息以前使用名为 dsymutil
的工具收集到单个 .dSYM
文件夹中,这可能需要一些时间并占用相当多的磁盘空间。
将所有调试信息收集到此目录中有助于在运行时找到它,特别是如果二进制文件被移动。但是,它确实有一个缺点,即使您对程序进行了很小的更改,dsymutil
也需要对整个最终二进制文件运行才能生成最终的 .dSYM
文件夹。这有时会增加构建时间,特别是对于大型项目而言,因为所有依赖项始终会被重新收集,但这一直是必要的步骤,因为如果没有它,Rust 的标准库不知道如何在 macOS 上加载调试信息。
最近,Rust 回溯切换到使用不同的后端,该后端支持加载调试信息,而无需运行 dsymutil
,并且我们已经稳定了对跳过 dsymutil
运行的支持。这可以显着加快包含调试信息的构建速度,并显着减少使用的磁盘空间。我们还没有进行广泛的基准测试,但已经看到很多关于人们的构建使用此行为在 macOS 上快得多的报告。
您可以通过在运行 rustc
时设置 -Csplit-debuginfo=unpacked
标志,或通过在 Cargo 中将 split-debuginfo
[profile]
选项设置为 unpacked
来启用此新行为。“unpacked” 选项指示 rustc 将 .o 对象文件保留在构建输出目录中,而不是删除它们,并跳过运行 dsymutil 的步骤。Rust 的回溯支持足够智能,知道如何找到这些 .o 文件。诸如 lldb 之类的工具也知道如何做到这一点。只要您不需要将二进制文件移动到不同的位置,同时保留调试信息,这应该可以正常工作。
[profile.dev]
split-debuginfo = "unpacked"
稳定化的 API
总的来说,此版本稳定了 18 种针对各种类型(如 slice
和 Peekable
)的新方法。一个值得注意的补充是 ptr::addr_of!
和 ptr::addr_of_mut!
的稳定化,它们允许您创建指向未对齐字段的原始指针。以前这是不可能的,因为 Rust 要求 &/&mut
对齐并指向已初始化的数据,而 &addr as *const _
则会导致未定义的行为,因为 &addr
需要对齐。这两个宏现在允许您安全地创建未对齐的指针。
use std::ptr;
#[repr(packed)]
struct Packed {
f1: u8,
f2: u16,
}
let packed = Packed { f1: 1, f2: 2 };
// `&packed.f2` would create an unaligned reference, and thus be Undefined Behavior!
let raw_f2 = ptr::addr_of!(packed.f2);
assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
以下方法已稳定。
Arc::decrement_strong_count
Arc::increment_strong_count
Once::call_once_force
Peekable::next_if_eq
Peekable::next_if
Seek::stream_position
array::IntoIter
panic::panic_any
ptr::addr_of!
ptr::addr_of_mut!
slice::fill_with
slice::split_inclusive_mut
slice::split_inclusive
slice::strip_prefix
slice::strip_suffix
str::split_inclusive
sync::OnceState
task::Wake
其他更改
Rust 1.51.0 版本中还有其他更改:查看 Rust、Cargo 和 Clippy 中发生了哪些变化。
1.51.0 的贡献者
许多人共同创建了 Rust 1.51.0。没有你们,我们无法做到。 感谢!