宣布 Rust 1.66.0

2022 年 12 月 15 日 · Rust 发布团队

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

如果您通过 rustup 安装了旧版本的 Rust,可以使用以下命令获取 1.66.0

$ rustup update stable

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

如果您想通过测试未来版本来帮助我们,可以考虑更新本地版本以使用 beta 通道 (rustup default beta) 或 nightly 通道 (rustup default nightly)。请报告您遇到的任何错误!

1.66.0 稳定版中的新功能

带字段的枚举的显式判别式

具有整数表示的枚举现在可以使用显式判别式,即使它们具有字段。

#[repr(u8)]
enum Foo {
    A(u8),
    B(i8),
    C(bool) = 42,
}

以前,您可以在具有表示的枚举上使用显式判别式,但前提是它们的变体都没有字段。显式判别式在跨语言边界传递值时很有用,在这种情况下,枚举的表示需要在两种语言中匹配。例如,

#[repr(u8)]
enum Bar {
    A,
    B,
    C = 42,
    D,
}

这里 Bar 枚举保证具有与 u8 相同的布局。此外,Bar::C 变体保证具有 42 的判别式。没有显式指定值的变体将具有根据其在源代码中的顺序自动分配的判别式,因此 Bar::A 将具有 0 的判别式,Bar::B 将具有 1 的判别式,Bar::D 将具有 43 的判别式。如果没有此功能,设置 Bar::C 显式值的唯一方法是在它之前添加 41 个不必要的变体!

注意:对于无字段的枚举,可以通过 as 转换(例如 Bar::C as u8)来检查判别式,Rust 没有提供语言级方法来访问带字段的枚举的原始判别式。相反,目前必须使用不安全代码来检查带字段的枚举的判别式。由于此功能旨在与需要不安全代码的跨语言 FFI 一起使用,因此这希望不会带来太多额外负担。同时,如果您只需要一个对判别式的隐藏句柄,请参阅 std::mem::discriminant 函数。

core::hint::black_box

在对编译器生成的机器代码进行基准测试或检查时,通常需要防止优化在某些地方发生。在以下示例中,函数 push_cap 在循环中执行 Vec::push 4 次

fn push_cap(v: &mut Vec<i32>) {
    for i in 0..4 {
        v.push(i);
    }
}

pub fn bench_push() -> Duration { 
    let mut v = Vec::with_capacity(4);
    let now = Instant::now();
    push_cap(&mut v);
    now.elapsed()
}

如果您检查编译器在 x86_64 上的优化输出,您会注意到它看起来相当短

example::bench_push:
  sub rsp, 24
  call qword ptr [rip + std::time::Instant::now@GOTPCREL]
  lea rdi, [rsp + 8]
  mov qword ptr [rsp + 8], rax
  mov dword ptr [rsp + 16], edx
  call qword ptr [rip + std::time::Instant::elapsed@GOTPCREL]
  add rsp, 24
  ret

事实上,我们想要基准测试的整个函数 push_cap 已经被优化掉了!

我们可以使用新稳定的 black_box 函数来解决这个问题。从功能上讲,black_box 并不十分有趣:它接受您传递给它的值并将其直接传递回来。但是,在内部,编译器将 black_box 视为一个可以对其输入执行任何操作并返回任何值的函数(正如其名称所暗示的那样)。

这对禁用像我们上面看到的优化非常有用。例如,我们可以提示编译器,在 for 循环的每次迭代之后,向量实际上将用于某些东西。

use std::hint::black_box;

fn push_cap(v: &mut Vec<i32>) {
    for i in 0..4 {
        v.push(i);
        black_box(v.as_ptr());
    }
}

现在我们可以在优化的汇编输出中找到展开的 for 循环

  mov dword ptr [rbx], 0
  mov qword ptr [rsp + 8], rbx
  mov dword ptr [rbx + 4], 1
  mov qword ptr [rsp + 8], rbx
  mov dword ptr [rbx + 8], 2
  mov qword ptr [rsp + 8], rbx
  mov dword ptr [rbx + 12], 3
  mov qword ptr [rsp + 8], rbx

您还可以看到在此汇编输出中调用 black_box 的副作用。指令 mov qword ptr [rsp + 8], rbx 在每次迭代之后都无用地重复。此指令将地址 v.as_ptr() 作为函数的第一个参数写入,该函数实际上从未被调用。

请注意,生成的代码根本不关心 push 调用引入的分配可能性。这是因为编译器仍在使用我们在 bench_push 函数中调用 Vec::with_capacity(4) 的事实。您可以尝试更改 black_box 的位置,或者尝试在多个地方使用它,以查看它对编译器优化的影响。

cargo remove

在 Rust 1.62.0 中,我们引入了 cargo add,这是一个命令行实用程序,用于将依赖项添加到您的项目中。现在,您可以使用 cargo remove 来删除依赖项。

稳定的 API

其他更改

Rust 1.66 版本中还有其他更改,包括

  • 您现在可以在模式中使用 ..=X 范围。
  • Linux 构建现在分别使用 LTO 和 BOLT 优化 rustc 前端和 LLVM 后端,从而提高了运行时性能和内存使用率。

查看RustCargoClippy 中的所有更改。

1.66.0 的贡献者

许多人共同创建了 Rust 1.66.0。没有你们,我们不可能做到。 感谢!