Rust 新版解读 | 1.60 | 重点: 查看 Cargo 构建耗时详情、Cargo Feature 增加新语法

原文链接: https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html

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

$ rustup update stable

基于源码的代码覆盖

rustc 新增了基于 LLVM 的代码覆盖率测量,想要测试的同学可以通过以下方式重新构建你的项目:

$ RUSTFLAGS="-C instrument-coverage" cargo build

运行新生成的可执行文件将在当前目录下产生一个 default.profraw 文件( 路径和文件名可以通过环境变量进行覆盖 )。

llvm-tools-preview 组件包含了 llvm-profdata,可以用于处理和合并原生的测量结果输出raw profile output)(测量区域执行数)。

llvm-cov 用于报告生成,它将 llvm-profdata 处理后的输出跟二进制可执行文件自身相结合,对于前者大家可能好理解,但是为何要跟后者可执行文件相结合呢?原因在于可执行文件中嵌入了一个从计数器到实际源代码单元的映射。

rustup component add llvm-tools-preview
$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata merge -sparse default.profraw -o default.profdata
$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-cov show -Xdemangler=rustfilt target/debug/coverage-testing \
    -instr-profile=default.profdata \
    -show-line-counts-or-regions \
    -show-instantiations

基于一个简单的 hello world 可执行文件,执行以上命令就可以获得如下带有标记的结果:

1|      1|fn main() {
2|      1|    println!("Hello, world!");
3|      1|}

从结果中可以看出:每一行代码都已经被成功覆盖。

如果大家还想要了解更多,可以看下官方的 rustc 文档。目前来说,基准功能已经稳定了,并将以某种形式存在于未来所有的 Rust 发布版本中。 但输出格式和产生这些输出的 LLVM 工具可能依然会发生变化,基于此,大家在使用时需要确保 llvm-tools-preview 和 rustc ( 用于编译代码的 )使用了相同的版本。

查看 Cargo 构建耗时

新版本中,以下命令已经可以正常使用了:

$ cargo build --timings
   Compiling hello-world v0.1.0 (hello-world)
      Timing report saved to target/cargo-timings/cargo-timing-20220318T174818Z.html
    Finished dev [unoptimized + debuginfo] target(s) in 0.98s

此命令会生成一个 cargo build 的耗时详情报告,除了上面提到的路径外,报告还会被拷贝到 target/cargo-timings/cargo-timing.html。这里是一个在线示例。该报告在你需要提升构建速度时会非常有用,更多的信息请查看文档

Cargo Feature 的新语法

关于 Cargo Features ,强烈推荐大家看看 Cargo 使用指南,可能是目前最好的中文翻译版本。

新版本为 Cargo Features 引入了两个新的语法: 命名空间 ( Namespaced )和弱依赖,它们可以让 features 跟可选依赖进行更好的交互。

Cargo 支持可选依赖已经很久了,例如以下代码所示:

[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false, optional = true }

[features]
# 通过开启 jpeg-decoder 依赖的 "rayon` feture,来启用并行化处理
parallel = ["jpeg-decoder/rayon"]

这个例子有两点值得注意:

  • 可选依赖 jpeg-decoder 隐式地定义了一个同名的 feature,当启用 jpeg-decoder feature 时将同时启用 jpeg-decoder
  • "jpeg-decoder/rayon" 语法会启用 jpeg-decoder 依赖,并且还会启用 jpeg-decoder 依赖的 rayon feature

而命名空间正是为了处理第一个问题而出现的。新版本中,我们可以在 [features] 中使用 dep: 前缀来显式地引用一个可选的依赖。再无需像第一点一样:先隐式的将可选依赖暴露为一个 feature,再通过 feature 来启用它。

这样一来,我们将能更好的定义可选依赖所对应的 feture,包括将可选依赖隐藏在一个更具描述性的 feature 名称后面。

弱依赖用于处理第二点: 根据第二点,optional-dependency/feature-name 必定会启用 optional-dependency 这个可选依赖。然而在一些场景中,我们只希望在其它 features 已经启用了可选依赖 optional-dependency 时才去启用 feature-name 这个 feature。

从 1.60 开始,我们可以使用 "package-name?/feature-name" 这种带有 ? 形式的语法: 只有当其它项已经启用了可选依赖 package-name 的情况下才去开启给定的 feature feature-name

译者注:简单来说,要启用 feature 必须需要别人先启用了其前置的可选依赖,再也无法像之前的第二点一样,既能开启可选依赖,又能启用 feature。

例如,我们希望为自己的库增加一些序列化功能,它需要开启某个可选依赖中的指定 feature,可以这么做:

[dependencies]
serde = { version = "1.0.133", optional = true }
rgb = { version = "0.8.25", optional = true }

[features]
serde = ["dep:serde", "rgb?/serde"]

这里定义了以下关系:

  1. 开启 serde feature 将启用可选的 serde 依赖
  2. 只有当 rgb 依赖在其它地方已经被启用后,此处才能启用 rgbserde feature

增量编译重启开启

1.59 更新说明中,我们有提到因为某些问题,增量编译被默认关闭了,现在官方修复了其中一些,并且确认目前的状态不会再影响用户的使用,因此在 1.60 版本中,增量编译又重新默认开启了。

Instant 单调性保证

译者注:Instant 可以获取当前的时间,因此保证其单调增长是非常重要的,例如 uuid 的生成往往依赖于时间戳的单调增长,一旦时间回退,就可能出现 uuid 重复的情况。

在目前所有的平台上,Instant 会去尝试使用系统提供的 API 来保证单调性行为( 目前主要针对 tier 1 的平台 )。然而在实际场景中,这种单调性偶尔会因为硬件、虚拟化或操作系统bug 等原因而失效。

为了解决这些失效或是平台没有提供 API 的情况,Instant::duration_since, Instant::elapsedInstant::sub 现在饱和为零( 这里不太好翻译,原文是 now saturate to zero,大概意思是非负?)。而在老版本中,这种时间回退的情况会导致 panic。

Instant::checked_duration_since 也可以用于检测和处理单调性失败或 Instants 的减法顺序不正确的情况。

但是目前的解决方法会遮掩一些错误的发生,因此在未来版本中,Rust 可能会重新就某些场景引入 panic 机制。

在 1.60 版本前,单调性主要通过标准库的互斥锁 Mutex 或原子性 atomic 来保证,但是在 Instant::now() 调用频繁时,可能会导致明显的性能问题。