诊断属性

diagnostics.md
commit: 12a4832c8eec1ad0df3edfb681571821708e0410
本章译文最后维护日期:2021-4-6

以下属性用于在编译期间控制或生成诊断消息。

lint检查类属性

(译者注:lint在原文里有时当名词用,有时当动词用,本文统一翻译成名词,意思就是一种被命名的 lint检查模式)

lint检查(lint check)系统命名了一些潜在的不良编码模式,(这些被命名的 lint检查就是一个一个的lint,)例如编写了不可能执行到的代码,就被命名为 unreachable-code lint,编写未提供文档的代码就被命名为 missing_docs lint。allowwarndenyforbid 这些能调整代码检查级别的属性被称为 lint级别属性,它们使用 MetaListPaths元项属性句法来指定接受此级别的各种 lint。代码实体应用了这些带了具体 lint 列表的 lint级别属性,编译器或相关代码检查工具就可以结合这两层属性对这段代码执行相应的代码检查和检查报告。

对任何名为 C 的 lint 来说:

  • allow(C) 会压制对 C 的检查,那这样的违规行为就不会被报告,
  • warn(C) 警告违反 C 的,但继续编译。
  • deny(C) 遇到违反 C 的情况会触发编译器报错(signals an error),
  • forbid(C)deny(C) 相同,但同时会禁止以后再更改 lint级别,

注意:可以通过 rustc -W help 找到所有 rustc 支持的 lint,以及它们的默认设置,也可以在 rustc book 中找到相关文档。

#![allow(unused)]
fn main() {
pub mod m1 {
    // 这里忽略未提供文档的编码行为
    #[allow(missing_docs)]
    pub fn undocumented_one() -> i32 { 1 }

    // 未提供文档的编码行为在这里将触发编译器告警
    #[warn(missing_docs)]
    pub fn undocumented_too() -> i32 { 2 }

    // 未提供文档的编码行为在这里将触发编译器报错
    #[deny(missing_docs)]
    pub fn undocumented_end() -> i32 { 3 }
}
}

Lint属性可以覆盖上一个属性指定的级别,但该级别不能更改禁止性的 Lint。这里的上一个属性是指语法树中更高级别的属性,或者是同一实体上的前一个属性,按从左到右的源代码顺序列出。

此示例展示了如何使用 allowwarn 来打开和关闭一个特定的 lint检查:

#![allow(unused)]
fn main() {
#[warn(missing_docs)]
pub mod m2{
    #[allow(missing_docs)]
    pub mod nested {
        // 这里忽略未提供文档的编码行为
        pub fn undocumented_one() -> i32 { 1 }

        // 尽管上面允许,但未提供文档的编码行为在这里将触发编译器告警
        #[warn(missing_docs)]
        pub fn undocumented_two() -> i32 { 2 }
    }

    // 未提供文档的编码行为在这里将触发编译器告警
    pub fn undocumented_too() -> i32 { 3 }
}
}

此示例展示了如何使用 forbid 来禁止对该 lint 使用 allow

#![allow(unused)]
fn main() {
#[forbid(missing_docs)]
pub mod m3 {
    // 试图切换到 allow级别将触发编译器报错
    #[allow(missing_docs)]
    /// 返回 2。
    pub fn undocumented_too() -> i32 { 2 }
}
}

注意:rustc允许在命令行上设置 lint级别,也支持在setting caps中设置 lint报告中的caps。

lint分组

lint可以被组织成不同命名的组,以便相关lint的等级可以一起调整。使用命名组相当于给出该组中的 lint。

#![allow(unused)]
fn main() {
// 这允许 "unused"组下的所有lint。
#[allow(unused)]
// 这个禁止动作将覆盖前面 "unused"组中的 "unused_must_use" lint。
#[deny(unused_must_use)]
fn example() {
    // 这里不会生成告警,因为 "unused_variables" lint 在 "unused"组下
    let x = 1;
    // 这里会产生报错信息,因为 "unused_must_use" lint 被标记为"deny",而这行的返回结果未被使用
    std::fs::remove_file("some_file"); // ERROR: unused `Result` that must be used
}
}

有一个名为 “warnings” 的特殊组,它包含 “warn”级别的所有 lint。“warnings”组会忽略属性的顺序,并对实体执行所有告警lint 检查。

#![allow(unused)]
fn main() {
unsafe fn an_unsafe_fn() {}
// 这里的两个属性的前后顺序无所谓
#[deny(warnings)]
// unsafe_code lint 默认是 "allow" 的。
#[warn(unsafe_code)]
fn example_err() {
    // 这里会编译报错,因为 `unsafe_code`告警的 lint 被提升为 "deny"级别了。
    unsafe { an_unsafe_fn() } // ERROR: usage of `unsafe` block
}
}

Tool lint attributes

工具类lint属性

可以为 allowwarndenyforbid 这些调整代码检查级别的 lint属性添加/输入基于特定工具的 lint。注意该工具在当前作用域内可用才会起效。

工具类lint 只有在相关工具处于活动状态时才会做相应的代码模式检查。如果一个 lint级别属性,如 allow,引用了一个不存在的工具类lint,编译器将不会去警告不存在该 lint类,只有使用了该工具(,才会报告该 lint类不存在)。

在其他方面,这些工具类lint 就跟像常规的 lint 一样:

// 将 clippy 的整个 `pedantic` lint组设置为告警级别
#![warn(clippy::pedantic)]
// 使来自 clippy的 `filter_map` lint 的警告静音
#![allow(clippy::filter_map)]

fn main() {
    // ...
}

// 使 clippy的 `cmp_nan` lint 静音,仅用于此函数
#[allow(clippy::cmp_nan)]
fn foo() {
    // ...
}

注意:rustc 当前识别的工具类lint 有 "clippy" 和 "rustdoc"。

The deprecated attribute

deprecated属性

deprecated属性将程序项标记为已弃用。rustc 将对被 #[deprecated] 限定的程序项的行为发出警告。rustdoc 将特别展示已弃用的程序项,包括展示 since 版本和 note 提示(如果可用)。

deprecated属性有几种形式:

  • deprecated — 发布一个通用的弃用消息。
  • deprecated = "message" — 在弃用消息中包含给定的字符串。
  • MetaListNameValueStr元项属性句法形式里带有两个可选字段:
    • since — 指定程序项被弃用时的版本号。rustc 目前不解释此字符串,但是像 Clippy 这样的外部工具可以检查此值的有效性。
    • note — 指定一个应该包含在弃用消息中的字符串。这通常用于提供关于不推荐的解释和推荐首选替代。

deprecated属性可以应用于任何程序项trait项枚举变体结构体字段外部块项,或宏定义。它不能应用于 trait实现项。当应用于包含其他程序项的程序项时,如模块实现,所有子程序项都继承此 deprecation属性。

这有个例子:

#![allow(unused)]
fn main() {
#[deprecated(since = "5.2", note = "foo was rarely used. Users should instead use bar")]
pub fn foo() {}

pub fn bar() {}
}

RFC 内有动机说明和更多细节。

The must_use attribute

must_use属性

must_use属性 用于在值未被“使用”时发出诊断告警。它可以应用于用户定义的复合类型(结构体(struct)枚举(enum)联合体(union))、函数trait

must_use属性可以使用MetaNameValueStr元项属性句法添加一些附加消息,如 #[must_use = "example message"]。该字符串将出现在告警消息里。

当用户定义的复合类型上使用了此属性,如果有该类型的表达式正好是表达式语句的表达式,那就违反了 unused_must_use 这个 lint。

#![allow(unused)]
fn main() {
#[must_use]
struct MustUse {
    // 一些字段
}

impl MustUse {
  fn new() -> MustUse { MustUse {} }
}

// 违反 `unused_must_use` lint。
MustUse::new();
}

当函数上使用了此属性,如果此函数被当作表达式语句表达式调用表达式,那就违反了 unused_must_use lint。

#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }

// 违反 unused_must_use lint。
five();
}

当 [trait声明]中使用了此属性,如果表达式语句调用表达式返回了此 trait 的 trait实现(impl trait) ,则违反了 unused_must_use lint。

#![allow(unused)]
fn main() {
#[must_use]
trait Critical {}
impl Critical for i32 {}

fn get_critical() -> impl Critical {
    4i32
}

// 违反 `unused_must_use` lint。
get_critical();
}

当 trait声明中的一函数上使用了此属性时,如果调用表达式是此 trait 的某个实现中的该函数时,该行为同样违反 unused_must_use lint。

#![allow(unused)]
fn main() {
trait Trait {
    #[must_use]
    fn use_me(&self) -> i32;
}

impl Trait for i32 {
    fn use_me(&self) -> i32 { 0i32 }
}

// 违反 `unused_must_use` lint。
5i32.use_me();
}

当在 trait实现里的函数上使用 must_use属性时,此属性将被忽略。

注意:包含了此(属性应用的程序项产生的值)的普通空操作(no-op)表达式不会违反该 lint。例如,将此类值包装在没有实现 Drop 的类型中,然后不使用该类型,并成为未使用的块表达式的最终表达式(final expression)。

#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }

// 这些都不违反 unused_must_use lint。
(five(),);
Some(five());
{ five() };
if true { five() } else { 0i32 };
match true {
    _ => five()
};
}

注意:当一个必须使用的值被故意丢弃时,使用带有模式 _let语句是惯用的方法。

#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }

// 不违反 unused_must_use lint。
let _ = five();
}