Rust资讯:新功能!在编译时自动检查 cfg

2,317 阅读4分钟

原文链接

2024年5月6日 · Urgau 代表 Cargo 团队

Cargo 和编译器团队很高兴地宣布,从 Rust 1.80(或 nightly-2024-05-05)开始,每个 可达#[cfg] 都将 自动检查 它们是否符合 预期的配置名称和值

这有助于验证 crate 是否正确处理了针对不同目标平台或功能的条件编译。它确保 cfg 设置在意图和使用之间是一致的,有助于在开发过程的早期发现潜在的错误或问题。

这解决了新用户和高级用户常见的陷阱。

这是我们致力于提供以用户为中心的工具的又一步,经过超过两年的等待,我们终于看到了它的解决方案,RFC 30131

功能概览

每次声明 Cargo 特性时,该特性会转换为传递给 rustc(Rust 编译器)的配置,以便它可以与 已知的 cfg 一起验证 #[cfg]#![cfg_attr]cfg! 是否有意外的配置,并通过 unexpected_cfgs lint 报告警告。

Cargo.toml:

[package]
name = "foo"

[features]
lasers = []
zapping = []

src/lib.rs:

#[cfg(feature = "lasers")]  // 该条件是预期的
                            // 因为 "lasers" 是 `feature` cfg 的预期值
fn shoot_lasers() {}

#[cfg(feature = "monkeys")] // 该条件是意外的
                            // 因为 "monkeys" 不是 `feature` cfg 的预期值
fn write_shakespeare() {}

#[cfg(windosw)]             // 该条件是意外的
                            // 它应该是 `windows`
fn win() {}

cargo check:

cargo-check

预期的自定义 cfg

更新:本节已在 nightly-2024-05-19 版本中添加。

在 Cargo 视角下:自定义 cfg 是指既不是由 rustc 定义的,也不是由 Cargo 特性定义的。比如 tokio_unstablehas_foo 等,但不是 feature = "lasers"unixdebug_assertions

有些 crate 可能使用自定义 cfg,如 loomfuzzingtokio_unstable,这些是从环境(RUSTFLAGS 或其他方式)中获取并在编译时始终静态已知的。在这些情况下,Cargo 通过 [lints] 表提供了一种方法来静态声明这些 cfg 为预期的。

通过在 [lints.rust.unexpected_cfgs] 下的特殊 check-cfg 配置定义这些自定义 cfg 为预期的:

Cargo.toml

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)', 'cfg(fuzzing)'] }

构建脚本中的自定义 cfg

另一方面,有些 crate 使用的自定义 cfg 是通过 crate build.rs 中的某些逻辑启用的。对于这些 crate,Cargo 提供了一个新指令:cargo::rustc-check-cfg2(或旧版本的 Cargo 使用 cargo:rustc-check-cfg)。

--check-cfg 的语法在 rustc 手册检查配置 一节中有描述,但简而言之,--check-cfg 的基本语法是:

cfg(name, values("value1", "value2", ..., "valueN"))

请注意,每个自定义 cfg 必须始终是预期的,无论 cfg 是否激活!

build.rs 示例

build.rs:

fn main() {
    println!("cargo::rustc-check-cfg=cfg(has_foo)");
    //        ^^^^^^^^^^^^^^^^^^^^^^ Cargo 1.80 中新增
    if has_foo() {
        println!("cargo::rustc-cfg=has_foo");
    }
}

每个 cargo::rustc-cfg 都应有一个无条件cargo::rustc-check-cfg 指令,以避免出现诸如 unexpected cfg condition name: has_foo 这样的警告。

等效表

cargo::rustc-cfgcargo::rustc-check-cfg
foocfg(foo)cfg(foo, values(none()))
foo=""cfg(foo, values(""))
foo="bar"cfg(foo, values("bar"))
foo="1"foo="2"cfg(foo, values("1", "2"))
foo="1"bar="2"cfg(foo, values("1"))cfg(bar, values("2"))
foofoo="bar"cfg(foo, values(none(), "bar"))

更多详情请参见 rustc 手册

常见问题

可以禁用它吗?

对于 Cargo 用户,该功能 始终开启不能禁用,但像其他 lint 一样,它可以被控制:#![warn(unexpected_cfgs)]

该 lint 会影响依赖项吗?

不会,像大多数 lint 一样,unexpected_cfgs 只会针对本地包报告,感谢 cap-lints

它如何与 RUSTFLAGS 环境变量交互?

你应该能够像以前一样使用 RUSTFLAGS 环境变量。目前 --cfg 参数不会被检查,只有代码中的使用会被检查。

这意味着执行 RUSTFLAGS="--cfg tokio_unstable" cargo check 不会报告任何警告,除非 tokio_unstable 在你的本地 crate 中被使用,在这种情况下,crate 作者需要确保该自定义 cfg 在该 crate 的 build.rs 中通过 cargo::rustc-check-cfg 被预期。

如何在没有 build.rs 的情况下预期自定义 cfg?

更新:Cargo 在 nightly-2024-05-19 中提供了 unexpected_cfgs.check-cfg 配置来解决静态已知的自定义 cfg。

目前除了在 build.rs 中使用 cargo::rustc-check-cfg 外,没有 其他方法预期自定义 cfg。

不想使用 build.rs 且不能使用 [lints.r ust.unexpected_cfgs.check-cfg] 的 crate 作者,建议使用 Cargo 特性。

它如何与其他构建系统交互?

非 Cargo 构建系统默认不会受到该 lint 的影响。希望具有相同功能的构建系统作者应查看 rustc 文档中 --check-cfg 标志的详细说明,以了解如何实现相同的功能。

  1. 稳定实现和 RFC 3013 差异显著,特别是 --check-cfg 只有一种形式:cfg()(而不是 values()names() 的形式不完整且微妙地不兼容)。
  2. cargo::rustc-check-cfg 将在 Rust 1.80(或 nightly-2024-05-05)中开始工作。从 Rust 1.77 到 Rust 1.79 (含) 被静默忽略。在 Rust 1.76 及以下版本中,使用不带不稳定 Cargo 标志 -Zcheck-cfg 时会发出警告。