rustc Lint 级别完全指南:allow、expect、warn、force-warn、deny 与 forbid 的语义、配置方法与优先级
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
本指南以 Rust 编译器(rustc)文档 levels.md 为核心,系统讲解 rustc 中六种 lint 级别的语义差异、两种配置入口(命令行 flag 与源码属性)、--cap-lints限制机制以及多来源之间的优先级规则。读完本文,你将能精准控制任意 lint 的行为——从"默认沉默"到"不可覆盖的错误",并理解其背后的编译器实现原理,可直接用于日常开发与 CI 严格化配置。
六个 lint 级别总览
在rustc中,lint 被划分为六个级别(levels),每个 lint 都有一个默认级别(可在 lint 列表 中查阅),编译器本身也有一个默认的警告级别。六个级别由低到高依次为:
| 级别 | 触发时的行为 | 能否被覆盖 |
|---|---|---|
allow | 什么都不做,静默放行 | 可 |
expect | 抑制 lint,但若 lint 未触发则报告"期望未满足" | 仅通过#[expect]属性设置 |
warn | 产生警告,编译继续 | 可 |
force-warn | 产生警告,且不可被覆盖(--cap-lints也无效) | 否 |
deny | 产生错误,lint 检查完成后停止执行 | 可 |
forbid | 产生错误,且不可被降级覆盖(--cap-lints仍可封顶) | 否 |
下面逐一详解每个级别的含义,然后再讨论配置方式。
allow:存在的 lint,但默认什么都不做
处于allow级别的 lint 是真实存在、并且会在编译时被检查的,只是默认不产生任何输出。例如下面这段源码:
pub fn foo() {}编译它不会产生任何警告:
$ rustc lib.rs --crate-type=lib $但这段代码实际上违反了missing_docslint(公共 API 缺少文档注释)。这类 lint 之所以存在,主要是为了让用户通过配置手动打开它们,正如本文后面"配置警告级别"一节所介绍的那样。missing_docs等默认允许的 lint 完整清单见 allowed-by-default 列表。
expect:抑制 lint,但确保它仍然"应该"被触发
有时候我们希望抑制某个 lint,但同时又想确认代码中确实存在触发该 lint 的问题——expect级别正是为此设计的。如果被#[expect]的 lint 最终没有触发,那么unfulfilled_lint_expectationslint 就会在expect属性上触发,提醒你这条"期望"已经失效了(可能是代码被修复,也可能是该 lint 不再适用)。
fn main() { #[expect(unused_variables)] let unused = "Everyone ignores me"; #[expect(unused_variables)] // `unused_variables` lint is not emitted let used = "I'm useful"; // the expectation is therefore unfulfilled println!("The `used` value is equal to: {:?}", used); }上面这段代码会输出如下警告:
warning: this lint expectation is unfulfilled --> src/main.rs:7:14 | 7 | #[expect(unused_variables)] | ^^^^^^^^^^^^^^^^ | = note: `#[warn(unfulfilled_lint_expectations)]` on by default关于expect级别有两点需要特别注意:
- 它只能通过
#[expect]属性定义,没有对应的命令行 flag 等价物; - 处于
force-warn特殊级别的 lint,即使被#[expect]包裹,仍然会照常发出警告(因为 force-warn 不可覆盖)。
expect机制的引入源自 RFC 2383,编译器内部通过LintExpectationId把一次 lint 发射(emission)与具体的期望关联起来,见 rustc_lint_defs 中的 Level 枚举 的相关注释。
warn:违反即警告
warn级别会在违反 lint 时产生警告,但编译流程继续执行。例如下面的代码触发了unused_variableslint:
pub fn foo() { let x = 5; }编译输出:
$ rustc lib.rs --crate-type=lib warning: unused variable: `x` --> lib.rs:2:9 | 2 | let x = 5; | ^ | = note: `#[warn(unused_variables)]` on by default = note: to avoid this warning, consider using `_x` instead注意输出中的两行note:第一行告诉你该 lint 的默认级别是warn,第二行给出编译器建议的修复方式(把x改成_x)。这也是绝大多数 lint 的默认工作方式,默认 warn 的 lint 清单见 warn-by-default 列表。
force-warn:不可覆盖的警告
force-warn是一个特殊的 lint 级别。它和warn一样,处于该级别的 lint 会产生警告,但和warn不同的是它无法被覆盖:一旦某个 lint 被设置为force-warn,它就保证一定产生警告——不多不少。即使整体 lint 级别通过--cap-lints被"封顶",force-warn的警告依然会发出。
这个级别的典型用途是编译器自身或构建系统强制要求某些信息必须可见,不允许用户(或下游配置)把它静默掉。由于它不可覆盖,一旦设置,后续针对同一 lint 的任何其他级别设置都不会生效。
deny:违反即错误
deny级别的 lint 在违反时会产生错误。例如下面的代码触发了exceeding_bitshiftslint:
fn main() { 100u8 << 10; }$ rustc main.rs error: bitshift exceeds the type's number of bits --> main.rs:2:13 | 2 | 100u8 << 10; | ^^^^^^^^^^^ | = note: `#[deny(exceeding_bitshifts)]` on by default那么,lint 产生的错误和普通的编译器错误有什么区别?区别在于:lint 错误是通过级别机制配置的。和allow级别的 lint 类似,默认就是deny的 lint 允许你把它降级为allow;同理,你也可以把一个默认warn的 lint 提升为错误。deny级别正是提供了这种灵活性。默认 deny 的 lint 清单见 deny-by-default 列表。
forbid:不可覆盖的错误
forbid是deny的特殊版本,两者关系正如force-warn之于warn。处于forbid级别的 lint 违反时产生错误,但和deny不同,它不能被覆盖为任何低于错误(error)的级别。不过 lint 级别仍然可以被--cap-lints封顶,所以rustc --cap-lints warn会让设置为forbid的 lint 退化为仅警告。
forbid特别适合用在库或项目的关键公共代码上——例如把unsafe_code设为forbid,可以确保整个 crate 无论后续配置如何变化都不会悄悄出现unsafe代码。
配置警告级别
还记得前面allow级别中的missing_docs例子吗?
$ cat lib.rs pub fn foo() {} $ rustc lib.rs --crate-type=lib $我们可以通过两种方式把这个 lint 配置到更高的级别:编译器命令行 flag与源码中的属性(attribute)。此外,还可以"封顶"(cap)lint 级别,让编译器忽略某些级别设置,这部分放在最后讲。
通过编译器 flag 配置
-A、-W、--force-warn、-D、-F五个 flag 分别可以把一个或多个 lint 设置为 allow、warn、force-warn、deny、forbid 级别。例如:
$ rustc lib.rs --crate-type=lib -W missing-docs warning: missing documentation for crate --> lib.rs:1:1 | 1 | pub fn foo() {} | ^^^^^^^^^^^^ | = note: requested on the command line with `-W missing-docs` warning: missing documentation for a function --> lib.rs:1:1 | 1 | pub fn foo() {} | ^^^^^^^^^^^^$ rustc lib.rs --crate-type=lib -D missing-docs error: missing documentation for crate --> lib.rs:1:1 | 1 | pub fn foo() {} | ^^^^^^^^^^^^ | = note: requested on the command line with `-D missing-docs` error: missing documentation for a function --> lib.rs:1:1 | 1 | pub fn foo() {} | ^^^^^^^^^^^^ error: aborting due to 2 previous errors注意命令行输出中的requested on the command line with ...一行,它表明该级别是通过命令行 flag 设置的。
- 每个 flag 可以多次传参,用来同时修改多个 lint:
$ rustc lib.rs --crate-type=lib -D missing-docs -D unused-variables- 五个 flag 可以混用:
$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables- 命令行参数顺序会被考虑。下面的命令最终会 allow
unused-variables,因为-A是最后一个针对该 lint 的参数:
$ rustc lib.rs --crate-type=lib -D unused-variables -A unused-variables- 可以利用这个顺序行为,对某个 lint 组中的一个 lint 单独覆盖级别。下面的命令把
unused组的所有 lint 设为 deny,但显式把组内的unused-variables允许掉(注意:forbid无论顺序如何都优先):
$ rustc lib.rs --crate-type=lib -D unused -A unused-variables- 由于
force-warn和forbid不可覆盖,一旦设置了其中一个,后面针对同一 lint 的任何更高级别设置都不会生效。
关于 lint 组(lint group)的完整概念与组成,参见 lint groups 文档。也可以在安装了 rustc 的机器上直接运行rustc -W help查看当前编译器支持的所有 lint 组及其包含的具体 lint。
通过属性配置
除了命令行 flag,还可以用crate 级属性修改 lint 级别:
$ cat lib.rs #![warn(missing_docs)] pub fn foo() {} $ rustc lib.rs --crate-type=lib warning: missing documentation for crate --> lib.rs:1:1 | 1 | / #![warn(missing_docs)] 2 | | 3 | | pub fn foo() {} | |_______________^ | note: lint level defined here --> lib.rs:1:9 | 1 | #![warn(missing_docs)] | ^^^^^^^^^^^^ | warning: missing documentation for a function --> lib.rs:3:1 | 3 | pub fn foo() {} | ^^^^^^^^^^^^注意这里输出了note: lint level defined here,指向属性定义的位置。
warn、allow、deny、forbid四种属性都可以这样使用;没有方式用属性把 lint 设为force-warn(force-warn 只能通过命令行设置)。- 每个属性可以传入多个 lint:
#![warn(missing_docs, unused_variables)] pub fn foo() {}- 也可以同时使用多个属性:
#![warn(missing_docs)] #![deny(unused_variables)] pub fn foo() {}- 所有 lint 属性都支持额外的
reason参数,用来说明添加该属性的上下文原因。如果 lint 在定义级别被触发,这个 reason 会作为 lint 消息的一部分显示出来:
use std::path::PathBuf; pub fn get_path() -> PathBuf { #[allow(unused_mut, reason = "this is only modified on some platforms")] let mut file_name = PathBuf::from("git"); #[cfg(target_os = "windows")] file_name.set_extension("exe"); file_name }在上面的例子中,unused_mut在非 Windows 平台上是多余的,但开发者明确知道这一点,因此用带reason的#[allow]既抑制了警告,又为后来维护者留下了上下文说明。reason参数对应源码中LintLevelSource::Node的reason: Option<Symbol>字段(见 rustc_middle/src/lint.rs)。
封顶 lint(Capping lints)
rustc支持--cap-lints LEVELflag,它设置"lint 封顶层级"(lint cap level),即所有 lint 级别的最大值。以前面deny一节的代码为例:
fn main() { 100u8 << 10; }编译时把 lint 封顶到 warn:
$ rustc lib.rs --cap-lints warn warning: bitshift exceeds the type's number of bits --> lib.rs:2:5 | 2 | 100u8 << 10; | ^^^^^^^^^^^ | = note: `#[warn(exceeding_bitshifts)]` on by default warning: this expression will panic at run-time --> lib.rs:2:5 | 2 | 100u8 << 10; | ^^^^^^^^^^^ attempt to shift left with overflow现在它只警告而不再报错。还可以进一步把级别封顶为 allow,让所有 lint 都沉默:
$ rustc lib.rs --cap-lints allow $Cargo 重度使用这个特性:编译依赖时 Cargo 会传入--cap-lints allow,这样依赖自身的警告不会污染你的构建输出。但请注意,--cap-lints allow不会覆盖标记为force-warn的 lint——这一点在 rustc_session 的会话初始化逻辑 中也有体现:当warnings被 allow 或 lint_cap 为 allow 时,can_emit_warnings为 false,但 force-warn 的强制发射路径不受此开关约束。
lint 级别来源的优先级
Rust 允许通过多种来源设置 lint 级别(allow、warn、deny、forbid、force-warn):
- 属性(Attributes):
#[allow(...)]、#![deny(...)]等; - 命令行选项(Command-line options):
--cap-lints、--force-warn、-A、-W、-D、-F。
这些来源的交互规则如下:
1.--force-warn强制 lint 处于警告级别,优先于属性和所有其他 CLI flag。
#[forbid(unused_variables)] fn main() { let x = 42; }编译命令及其输出:
$ rustc --force-warn unused_variables lib.rs warning: unused variable: `x` --> lib.rs:3:9 | 3 | let x = 42; | ^ help: if this is intentional, prefix it with an underscore: `_x` | = note: requested on the command line with `--force-warn unused-variables` warning: 1 warning emitted注意:尽管源码里是#[forbid(unused_variables)],--force-warn依然把输出压成了警告。
2.--cap-lints设置 lint 的最大级别,优先于属性以及-D、-W、-F这些 CLI flag。
#[deny(unused_variables)] fn main() { let x = 42; }编译命令及其输出:
$ rustc --cap-lints=warn lib.rs warning: unused variable: `x` --> test1.rs:3:9 | 3 | let x = 42; | ^ help: if this is intentional, prefix it with an underscore: `_x` | note: the lint level is defined here --> test1.rs:1:8 | 1 | #[deny(unused_variables)] | ^^^^^^^^^^^^^^^^ warning: 1 warning emitted3. CLI 级别 flag 覆盖 lint 的默认级别,其行为本质上等同于 crate 级属性。源码中的属性优先于 CLI flag,但-F/--forbid除外——它不可被覆盖。flag 的顺序有意义,右边的 flag 优先于左边的。
fn main() { let x = 42; }编译命令及其输出:
$ rustc -A unused_variables -D unused_variables lib.rs error: unused variable: `x` --> test1.rs:2:9 | 2 | let x = 42; | ^ help: if this is intentional, prefix it with an underscore: `_x` | = note: requested on the command line with `-D unused-variables` error: aborting due to 1 previous error4. 在源码内部,语法树中层级更低的属性(作用域更小、更贴近节点的)优先于层级更高的属性;同一实体上的多个属性,按从左到右的源码顺序,后出现的优先。
#![deny(unused_variables)] #[allow(unused_variables)] fn main() { let x = 42; // Allow wins }在上面的代码中,crate 级#![deny]被函数级的#[allow]覆盖,因此x不会触发错误。
唯一的例外是forbid:一旦 lint 被设置为forbid,再试图改变它的级别就是错误;在 forbid 上下文中设置deny是被允许的语法,但会被忽略。
就优先级而言,lint 组(lint groups)会被当作展开成其包含的所有 lint 的列表来处理;唯一的例外是warnings组——它忽略属性与 CLI 的顺序,作用于该实体上所有本会警告的 lint。
源码视角:lint 级别在编译器内部如何落地
理解了语义和用法之后,不妨看一下 rustc 源码中这些概念是如何建模的,这有助于预测各种边界情况的行为。
六个级别的枚举定义位于 compiler/rustc_lint_defs/src/lib.rs:Level::Allow、Level::Expect、Level::Warn、Level::ForceWarn、Level::Deny、Level::Forbid,与本文介绍的六个级别一一对应。其文档注释还揭示了一些实现细节:
Expect不是 lint 的初始级别,它需要携带一个LintExpectationId,用于在 lint 实际发射时把"发射"与"期望"链接起来(RFC 2383 的落地);ForceWarn当前只能通过控制台(命令行)设置,因此是 session 相关的;Deny会"产生错误并在 lint pass 完成后停止进一步执行"。
级别来源的建模位于 compiler/rustc_middle/src/lint.rs:LintLevelSource枚举区分了三种来源——Default(rustc 声明的默认级别)、Node(属性设置,携带属性名、span 以及 RFC 2383 的 reason)、CommandLine(命令行 flag 设置,记录 flag 名和指定的级别)。每个 lint 的最终状态则由LevelSpec表示:一个Level+ 可选的LintExpectationId+LintLevelSource,其构造器会校验level/lint_id的组合合法性(比如只有Expect才允许携带 lint_id)。
层级解析的数据结构位于 compiler/rustc_lint/src/levels.rs:LintLevelSets维护一个LintSet的链表(IndexVec),每个LintSet对应 AST 上一个节点的属性集合,parent指针把不同层级的节点串起来,链表的起点是COMMAND_LINE = 0(命令行级别)。raw_lint_level_spec从当前节点开始沿着parent向上回溯查找 lint 的级别定义,找不到就返回命令行级别或默认值——这正是"内层属性优先于外层属性、命令行作为兜底"这一语义的代码级体现。
封顶的实现与会话(Session)的lint_cap配置相关,并在 rustc_session/src/session.rs 中参与决定can_emit_warnings,从而影响常规警告是否输出。
实践建议
- 库作者:对公共 API 开启
#![warn(missing_docs)],在 CI 中用-D warnings把警告升级为错误,防止文档缺失悄悄进入发布版本; - 团队统一标准:用
-D unused、-D nonstandard-style等 lint 组一次性收紧多个检查,再用顺序靠后的-A <specific-lint>对个别 lint 放行; - 关键安全项:对
unsafe_code等使用#![forbid(...)],确保无论后续配置如何都无法降级;对必须让用户看见的信息使用--force-warn; - 依赖管理:理解 Cargo 默认对依赖传入
--cap-lints allow的行为——依赖中的警告不会出现在你的构建输出里,因此不要在依赖编译结果中期待告警信息; - 演进代码:当 lint 不再适用时,及时移除
#[expect]属性,借助unfulfilled_lint_expectations警告保持期望与代码同步。
完整 lint 清单(按默认级别分组)与 lint 组定义,可继续阅读 lint 列表 和 lint groups 两个章节。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考