rustc 的 --check-cfg 如何检查条件编译:为 Cargo 项目声明期望的 cfg 名称与取值
2026/9/9 21:59:21 网站建设 项目流程

rustc 的 --check-cfg 如何检查条件编译:为 Cargo 项目声明期望的 cfg 名称与取值

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

#[cfg]条件写错了名字或取值——比如把windows拼成widnows、用了一个不存在的 feature——编译不会报错,那段代码只是静默地不再参与编译。rustc--check-cfg就是用来堵住这类问题的:你用 check cfg 声明式在编译前声明"哪些 cfg 名称和取值是预期的",编译时所有可达的#[cfg]都会对照这份期望清单,不匹配的名称或取值会触发unexpected_cfgslint,默认级别为Warn

本文基于 Checking conditional configurations 与 Cargo Specifics 两篇 rustc 文档,讲清楚如何在 rustc 命令行直接声明期望,以及 Cargo 项目(Cargo.tomlbuild.rs)中的三种对应机制。

--check-cfg 的声明语法

基本形式:

rustc --check-cfg 'cfg(name, values("value1", "value2", ... "valueN"))'

其中name是裸标识符(不带引号),values()里每一项是带引号的字符串字面量。name对应条件名称(如featuremy_cfg),每个"value"是该条件允许的取值之一。

几种常用变体:

  • 期望"无值"条件(即#[cfg(foo)]这种写法):在values()内使用none()谓词,它可以和任意数量的"value"混排:

    rustc --check-cfg 'cfg(name)' rustc --check-cfg 'cfg(name, values(none()))'
  • 只检查名称、不管取值,有两种相反语义:

    # 无期望值:name 的每个取值都会触发 lint rustc --check-cfg 'cfg(name, values())' # 任意值都接受:name 的取值永不触发 lint rustc --check-cfg 'cfg(name, values(any()))'
  • 多个名称共享同一取值集合时,把名称并列写在一条声明里,避免重复:

    rustc --check-cfg 'cfg(name1, ..., nameN, values("value1", "value2", ... "valueN"))'
  • 不指定任何名称和取值rustc --check-cfg 'cfg()'表示启用检查,且没有任何用户自定义期望。

几点行为边界,来自文档的明确说明:

  • 使用--cfg激活某个配置不会隐式加入期望清单;所有期望名称和取值都必须通过 check cfg 声明式显式给出。
  • 同一条件的--check-cfg参数可以重复出现,重复时各取值集合会合并,values(any())优先。
  • 命令行上已经激活的--cfg参数本身目前不被检查,文档注明未来可能改变。
  • 检查范围:rustc承诺至少检查所有可达的(reachable)#[cfg];不可达的#[cfg]当前不检查,但未来可能开始检查且不算破坏性变更。

哪些写法会被检查、诊断长什么样

只要指定了cfg(...)选项,以下四种写法都会逐一对照期望清单:

  • #[cfg(name = "value")]属性
  • #[cfg_attr(name = "value")]属性
  • #[link(name = "a", cfg(name = "value"))]属性
  • cfg!(name = "value")宏调用

当条件中出现不在期望清单里的取值(或名称)时,编译器报告unexpected_cfgslint 诊断。下面是文档给出的诊断示例(示例结果):

warning: unexpected `cfg` condition name: `widnows` --> lint_example.rs:1:7 | 1 | #[cfg(widnows)] | ^^^^^^^ | = note: `#[warn(unexpected_cfgs)]` on by default

也就是说,验证方式是观察编译输出中是否出现unexpected_cfgs警告:拼错的条件名、未声明的取值都会逐条列出文件位置;编译通过且无此警告,说明代码里用到的#[cfg]条件都落在声明的期望范围内。

文档示例:带 feature 的完整命令

文档给出的一条可参考的命令(example.rs为示意文件名,替换为你要检查的文件):

rustc --check-cfg 'cfg(feature, values("lion", "zebra"))' \ --cfg 'feature="lion"' example.rs

这条命令行表示:crate 有两个 featurelionzebra,当前启用的是lion。对应源码中的检查结果:

#[cfg(feature = "lion")] // 预期内:"lion" 在期望取值列表中 fn tame_lion(lion: Lion) {} #[cfg(feature = "zebra")] // 预期内:"zebra" 是期望取值,只是本次未激活,条件求值为 false fn ride_zebra(z: Zebra) {} #[cfg(feature = "platypus")] // 预期外:"platypus" 不在期望取值中,触发 unexpected_cfgs fn poke_platypus() {} #[cfg(feechure = "lion")] // 预期外:"feechure" 不是已声明的条件名称,触发 unexpected_cfgs fn tame_lion() {} #[cfg(windows = "unix")] // 预期外:well known 的 'windows' cfg 不接受任何值,触发 unexpected_cfgs fn tame_windows() {}

这个例子顺带展示了--check-cfg--cfg的分工:--cfg负责"激活"某个条件(决定条件是否为真),--check-cfg负责"声明预期"(决定条件是否合法)。

Well known names:一批名称无需手动声明

rustc维护了一份 well-known 名称及其取值的内置清单,只要至少传入一个--check-cfg参数,这些名称就会隐式加入期望。文档给出的清单(截至 2026-08-11)包括:

clippydebug_assertionsdocdoctestfmt_debugmirioverflow_checkspanicproc_macrorelocation_modelrust_analyzerrustfmtsanitizesanitizer_cfi_generalize_pointerssanitizer_cfi_normalize_integerstarget_abitarget_archtarget_endiantarget_envtarget_familytarget_object_formattarget_featuretarget_has_atomictarget_has_atomic_primitive_alignmenttarget_has_atomic_load_storetarget_ostarget_pointer_widthtarget_thread_localtarget_vendorub_checkscontract_checksunixwindows

因此#[cfg(target_arch = "x86_64")]这类目标相关条件不需要你逐个声明,但要注意:内置的windows/unix这类名称本身不带值,写成#[cfg(windows = "unix")]仍然会触发 lint。与values(any())类似,也可以给--check-cfg传入cfg(any())来关闭对 well-known 名称的检查。

另有一条版本相关的说明:从 1.85.0 起,testcfg 被归为"userspace"配置——尽管它同样由rustc设置,但文档认为它应当由构建系统自行管理(即由构建系统在--check-cfg中声明)。

Cargo 项目中的三种声明方式

Cargo 生态下,期望清单有三个声明位置,按配置来源的确定程度选择:

方式一:feature 由 Cargo 自动声明

Cargo.toml[features]表中每个 feature,Cargo 都会自动把对应 cfg 声明为期望:

[features] serde = ["dep:serde"] my_feature = []

所以对 feature 条件,通常不需要再手动写--check-cfg,只需检查代码中使用的 feature 名是否真的存在于[features]表。

方式二:Cargo.toml[lints.rust]表(静态已知的自定义 cfg)

当自定义配置是静态已知、不依赖 build script 时,可以在[lints.rust]下用unexpected_cfgscheck-cfg项写入自定义静态--check-cfg参数:

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

方式三:build.rs输出cargo::rustc-check-cfg(动态决定的自定义 cfg)

当自定义配置由 build script 动态决定、通过cargo::rustc-cfg激活时,对应的期望要用cargo::rustc-check-cfg指令声明(Cargo 1.80 新增):

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

排查与限制

  • 激活了但没声明--cfg只激活条件、不产生期望。代码里用到#[cfg(my_cfg)]时,必须另有--check-cfg 'cfg(my_cfg)'(或 Cargo 侧对应机制)声明,否则触发 lint。

  • --cfg--check-cfg的对应关系:文档给出了等价对照表,转换时按此表核对:

    --cfg对应的--check-cfg
    无,或--check-cfg=cfg()(仅启用检查)
    --cfg foo--check-cfg=cfg(foo)--check-cfg=cfg(foo, values(none()))
    --cfg foo=""--check-cfg=cfg(foo, values(""))
    --cfg foo="bar"--check-cfg=cfg(foo, values("bar"))
    --cfg foo="1" --cfg foo="2"--check-cfg=cfg(foo, values("1", "2"))
    --cfg foo="1" --cfg bar="2"--check-cfg=cfg(foo, values("1")) --check-cfg=cfg(bar, values("2"))
    --cfg foo --cfg foo="bar"--check-cfg=cfg(foo, values(none(), "bar"))
  • 检查范围是"可达"的#[cfg]:不可达条件当前不检查,但文档明确保留未来开始检查的可能,声明期望时不要依赖这个豁免。

  • nightly 辅助手段--print=check-cfg(配合-Zunstable-options,见 print-check-cfg)可以把编译器当前使用的配置输出为可复用的--check-cfg参数,适合用来核对实际生效的配置;这是 nightly 不稳定选项,不适用于稳定版构建流程。

配置完成后跑一次编译,编译输出里不再出现unexpected_cfgs警告、且代码中每个可达的#[cfg]名称与取值都在声明清单内,即说明期望声明与实际使用一致;新增 feature 或自定义 cfg 时,记得同步更新[features]表、[lints.rust]或 build script 中的声明。

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询