Rust 新版解读 | 1.66 | 重点: 有字段枚举的显示判别

Rust 1.66 官方 release doc: Announcing Rust 1.66.0 | Rust Blog

通过 rustup 安装的同学可以使用以下命令升级到 1.66 版本:

$ rustup update stable

对有字段枚举的显示判别

枚举的显示判别在跨语言传递值时很关键,需要两个语言里每个枚举值的判别是一致的,比如:

#![allow(unused)]
fn main() {
#[repr(u8)]
enum Bar {
    A,
    B,
    C = 42,
    D,
}
}

这个例子里,枚举 Bar 使用了 u8 作为原语表形(representation),并且 Bar::C 使用 42 来判别,其它没有显示判别的枚举值会按照源码里地顺序自动地递增赋值,这里的 Bar::A 是0,Bar::B 是1,Bar::D 是43。如果没有显示判别,那就只能在 Bar::BBar::C 之间加上 40 个无意义的枚举值了。

在1.66之前,枚举的显示判别只能用在无字段枚举上。现在对有字段枚举的显示判别也稳定了:

#![allow(unused)]
fn main() {
#[repr(u8)]
enum Foo {
    A(u8),
    B(i8),
    C(bool) = 42,
}
}

注意:可以通过 as 转换(比如 Bar::C as u8 )来判断一个无字段枚举的判别值,但是 Rust 还没有给有字段枚举提供语言层面上的获取原始判别值的方法,只能通过 unsafe 的代码来检查有字段枚举的判别值。考虑到这个使用场景往往出现在必须使用 unsafe 代码的跨语言的 FFI 里,希望这没有造成太大的负担。如果你的确需要的话,参考 std::mem::discriminant

黑盒方法 core::hint::black_box

当对编译器产生的代码做基准测试时,常常需要阻止一些优化,比如下面的代码里, push_cap 在一个循环里执行了4次 Vec::push

#![allow(unused)]
fn main() {
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 机器上编译的优化输出结果,你会注意到整个 push_cap 方法都被优化掉了...

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

现在可以通过调用 black_box 来避免类似情况的发送。 虽然实际上 black_box 内部只会取走值并直接返回,但是编译器会认为这个方法可能做任何事情。

#![allow(unused)]
fn main() {
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());
    }
}
}

这样就可以得到展开循环的结果

  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 指令都不用考虑内存分配的问题,这是因为编译器运行在 Vec::with_capacity(4) 的条件下。你可以尝试改动一下 black_box 的位置或者在多处使用,来看看其对编译的优化输出的影响。

cargo remove

1.62里我们引入了 cargo add 来通过命令行给你的项目增加依赖项。现在可以使用 cargo remove 来移除依赖了。

Others

其它更新细节,和稳定的API列表,参考原Blog