Rust 1.51.0 发布

2021年3月25日 · Rust 发布团队

Rust 团队很高兴地宣布 Rust 的新版本 1.51.0。Rust 是一种编程语言,它正在赋能每个人构建可靠且高效的软件。

如果您通过 rustup 安装了以前版本的 Rust,那么获取 Rust 1.51.0 非常简单,只需执行以下命令:

$ rustup update stable

如果您还没有安装 rustup,您可以从我们网站的相应页面获取 rustup,并查看 GitHub 上 1.51.0 的详细发行说明

1.51.0 稳定版的新功能

此版本代表了 Rust 语言和 Cargo 的一次重大更新,稳定了 const generics 的 MVP 和 Cargo 的新特性解析器。让我们深入了解一下!

Const Generics 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,您可以编写泛型化任何整数、boolchar 类型的值的代码!(使用 structenum 值仍然是不稳定的。)

此更改现在允许我们拥有自己的数组结构,该结构在其类型长度上都是泛型的。让我们看一个示例定义,以及如何使用它。

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]
}

Const generics 为库设计人员创建新的、强大的编译时安全 API 添加了一个重要的工具。如果您想了解有关 const generics 的更多信息,您还可以查看"Const Generics MVP Hits Beta"博客文章,以获取有关该功能及其当前限制的更多信息。我们迫不及待地想看看您创建的新库和 API!

array::IntoIter 稳定化

作为 const generics 稳定化的一部分,我们还稳定了一个使用它的新 API,std::array::IntoIterIntoIter 允许您创建对任何数组的按值迭代器。以前,没有方便的方法来迭代数组的拥有值,只能迭代对它们的引用。

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 功能,该依赖项被 barbaz 包使用,但 bar 依赖于 foo+A,而 baz 依赖于 foo+B。Cargo 将合并这两个功能并将 foo 编译为 foo+AB。这样做的好处是您只需编译 foo 一次,然后它就可以被 barbaz 重用。

但是,这也带来了缺点。如果在构建依赖项中启用的功能与您正在构建的目标不兼容怎么办?

生态系统中一个常见的例子是许多 #![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 以获得对行为的详细描述,其可以概括如下。

  • 开发依赖项 — 当一个包作为普通依赖项和开发依赖项共享时,只有在当前构建包含开发依赖项时才会启用开发依赖项功能。
  • 主机依赖项 — 当一个包作为普通依赖项和构建依赖项或过程宏共享时,普通依赖项的功能将与构建依赖项或过程宏的功能保持独立。
  • 目标依赖项 — 当一个包在构建图中多次出现,并且其中一个实例是特定于目标的依赖项时,只有在当前正在构建目标时才会启用特定于目标的功能。

虽然这可能会导致某些 crate 被编译多次,但这应该在使用 cargo 的功能时提供更加直观的开发体验。如果您想了解更多信息,还可以阅读 Cargo Book 中的 “Feature Resolver” 部分以获取更多信息。我们要感谢 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

总的来说,此版本稳定了各种类型(如 slicePeekable)的 18 个新方法。一个值得注意的补充是 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);

以下方法已稳定化。

其他更改

Rust 1.51.0 版本中还有其他更改:查看 RustCargoClippy 中的更改。

1.51.0 的贡献者

很多人齐心协力创造了 Rust 1.51.0。没有你们大家,我们不可能完成这项工作。 感谢!