诊断属性

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

Lint 检查属性

lint 检查命名了一种可能不受欢迎的编码模式,例如无法访问的代码或遗漏的文档。

lint 属性 allowexpectwarndenyforbid 使用 MetaListPaths 语法来指定 lint 名称列表,以更改该属性所应用于的实体的 lint 级别。

对于任何 lint 检查 C

  • #[allow(C)] 会覆盖对 C 的检查,以便违规行为不会被报告。
  • #[expect(C)] 表示预期会发出 lint C。如果期望未实现,该属性将抑制 C 的发出,或者发出警告。
  • #[warn(C)] 会对 C 的违规行为发出警告,但继续编译。
  • #[deny(C)] 在遇到 C 的违规行为后发出错误信号,
  • #[forbid(C)]deny(C) 相同,但也会禁止之后更改 lint 级别,

注意

rustc 支持的 lint 检查可以通过 rustc -W help 查看,以及它们的默认设置,并且在rustc 手册中进行了说明。

#![allow(unused)]
fn main() {
pub mod m1 {
    // Missing documentation is ignored here
    #[allow(missing_docs)]
    pub fn undocumented_one() -> i32 { 1 }

    // Missing documentation signals a warning here
    #[warn(missing_docs)]
    pub fn undocumented_too() -> i32 { 2 }

    // Missing documentation signals an error here
    #[deny(missing_docs)]
    pub fn undocumented_end() -> i32 { 3 }
}
}

Lint 属性可以覆盖先前属性指定的级别,只要该级别不尝试更改被禁止的 lint(除了 deny,它在 forbid 上下文中是允许的,但会被忽略)。先前的属性是指语法树中更高级别的属性,或者同一实体上按照从左到右源代码顺序排列的先前属性。

这个例子展示了如何使用 allowwarn 来开启或关闭某个检查

#![allow(unused)]
fn main() {
#[warn(missing_docs)]
pub mod m2 {
    #[allow(missing_docs)]
    pub mod nested {
        // Missing documentation is ignored here
        pub fn undocumented_one() -> i32 { 1 }

        // Missing documentation signals a warning here,
        // despite the allow above.
        #[warn(missing_docs)]
        pub fn undocumented_two() -> i32 { 2 }
    }

    // Missing documentation signals a warning here
    pub fn undocumented_too() -> i32 { 3 }
}
}

这个例子展示了如何使用 forbid 来禁止在该 lint 检查中使用 allowexpect

#![allow(unused)]
fn main() {
#[forbid(missing_docs)]
pub mod m3 {
    // Attempting to toggle warning signals an error here
    #[allow(missing_docs)]
    /// Returns 2.
    pub fn undocumented_too() -> i32 { 2 }
}
}

注意

rustc 允许在命令行上设置 lint 级别,也支持对报告的 lint 设置上限

Lint 原因

所有 lint 属性都支持一个额外的 reason 参数,以提供添加某个属性的上下文。如果 lint 在定义的级别发出,此原因将作为 lint 消息的一部分显示。

#![allow(unused)]
fn main() {
// `keyword_idents` is allowed by default. Here we deny it to
// avoid migration of identifiers when we update the edition.
#![deny(
    keyword_idents,
    reason = "we want to avoid these idents to be future compatible"
)]

// This name was allowed in Rust's 2015 edition. We still aim to avoid
// this to be future compatible and not confuse end users.
fn dyn() {}
}

这是另一个例子,其中 lint 因故被允许

#![allow(unused)]
fn main() {
use std::path::PathBuf;

pub fn get_path() -> PathBuf {
    // The `reason` parameter on `allow` attributes acts as documentation for the reader.
    #[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
}
}

#[expect] 属性

#[expect(C)] 属性为 lint C 创建一个 lint 期望。如果在同一位置的 #[warn(C)] 属性会导致发出 lint,则该期望将被满足。如果由于 lint C 不会被发出而导致期望未实现,则将在该属性处发出 unfulfilled_lint_expectations lint。

fn main() {
    // This `#[expect]` attribute creates a lint expectation, that the `unused_variables`
    // lint would be emitted by the following statement. This expectation is
    // unfulfilled, since the `question` variable is used by the `println!` macro.
    // Therefore, the `unfulfilled_lint_expectations` lint will be emitted at the
    // attribute.
    #[expect(unused_variables)]
    let question = "who lives in a pineapple under the sea?";
    println!("{question}");

    // This `#[expect]` attribute creates a lint expectation that will be fulfilled, since
    // the `answer` variable is never used. The `unused_variables` lint, that would usually
    // be emitted, is suppressed. No warning will be issued for the statement or attribute.
    #[expect(unused_variables)]
    let answer = "SpongeBob SquarePants!";
}

lint 期望仅由被 expect 属性抑制的 lint 发出行为满足。如果在作用域中使用 allowwarn 等其他级别属性修改了 lint 级别,lint 发出行为将相应处理,而期望将保持未实现状态。

#![allow(unused)]
fn main() {
#[expect(unused_variables)]
fn select_song() {
    // This will emit the `unused_variables` lint at the warn level
    // as defined by the `warn` attribute. This will not fulfill the
    // expectation above the function.
    #[warn(unused_variables)]
    let song_name = "Crab Rave";

    // The `allow` attribute suppresses the lint emission. This will not
    // fulfill the expectation as it has been suppressed by the `allow`
    // attribute and not the `expect` attribute above the function.
    #[allow(unused_variables)]
    let song_creator = "Noisestorm";

    // This `expect` attribute will suppress the `unused_variables` lint emission
    // at the variable. The `expect` attribute above the function will still not
    // be fulfilled, since this lint emission has been suppressed by the local
    // expect attribute.
    #[expect(unused_variables)]
    let song_version = "Monstercat Release";
}
}

如果 expect 属性包含多个 lint,则每个 lint 都是单独期望的。对于一个 lint 组,只要组内有一个 lint 发出就足够了

#![allow(unused)]
fn main() {
// This expectation will be fulfilled by the unused value inside the function
// since the emitted `unused_variables` lint is inside the `unused` lint group.
#[expect(unused)]
pub fn thoughts() {
    let unused = "I'm running out of examples";
}

pub fn another_example() {
    // This attribute creates two lint expectations. The `unused_mut` lint will be
    // suppressed and with that fulfill the first expectation. The `unused_variables`
    // wouldn't be emitted, since the variable is used. That expectation will therefore
    // be unsatisfied, and a warning will be emitted.
    #[expect(unused_mut, unused_variables)]
    let mut link = "https://rust-lang.net.cn/";

    println!("Welcome to our community: {link}");
}
}

注意

#[expect(unfulfilled_lint_expectations)] 的行为目前定义为始终生成 unfulfilled_lint_expectations lint。

Lint 组

Lints 可以组织到命名组中,以便可以一起调整相关 lints 的级别。使用命名组等同于列出该组内的 lints。

#![allow(unused)]
fn main() {
// This allows all lints in the "unused" group.
#[allow(unused)]
// This overrides the "unused_must_use" lint from the "unused"
// group to deny.
#[deny(unused_must_use)]
fn example() {
    // This does not generate a warning because the "unused_variables"
    // lint is in the "unused" group.
    let x = 1;
    // This generates an error because the result is unused and
    // "unused_must_use" is marked as "deny".
    std::fs::remove_file("some_file"); // ERROR: unused `Result` that must be used
}
}

有一个名为“warnings”的特殊组,它包括所有“warn”级别的 lints。“warnings”组忽略属性顺序,并适用于在该实体内本来会发出警告的所有 lints。

#![allow(unused)]
fn main() {
unsafe fn an_unsafe_fn() {}
// The order of these two attributes does not matter.
#[deny(warnings)]
// The unsafe_code lint is normally "allow" by default.
#[warn(unsafe_code)]
fn example_err() {
    // This is an error because the `unsafe_code` warning has
    // been lifted to "deny".
    unsafe { an_unsafe_fn() } // ERROR: usage of `unsafe` block
}
}

工具 lint 属性

工具 lint 允许使用作用域 lint,以便对某些工具的 lint 进行 allowwarndenyforbid

工具 lint 仅在相关工具处于活动状态时才会被检查。如果 lint 属性(例如 allow)引用了不存在的工具 lint,编译器在您使用该工具之前不会对不存在的 lint 发出警告。

否则,它们就像常规 lint 属性一样工作

// set the entire `pedantic` clippy lint group to warn
#![warn(clippy::pedantic)]
// silence warnings from the `filter_map` clippy lint
#![allow(clippy::filter_map)]

fn main() {
    // ...
}

// silence the `cmp_nan` clippy lint just for this function
#[allow(clippy::cmp_nan)]
fn foo() {
    // ...
}

注意

rustc 目前识别“clippy”和“rustdoc”的工具 lints。

deprecated 属性

deprecated 属性将一个项标记为已弃用。rustc 在使用 #[deprecated] 项时会发出警告。rustdoc 将显示项的弃用信息,包括 since 版本和 note(如果可用)。

deprecated 属性有几种形式

  • deprecated — 发出通用消息。
  • deprecated = "message" — 在弃用消息中包含给定的字符串。
  • MetaListNameValueStr 语法,带有两个可选字段
    • since — 指定项被弃用的版本号。rustc 目前不解释此字符串,但像 Clippy 这样的外部工具可能会检查该值的有效性。
    • note — 指定应包含在弃用消息中的字符串。这通常用于提供有关弃用和首选替代方案的解释。

deprecated 属性可以应用于任何trait 项枚举变体struct 字段外部块项宏定义。它不能应用于trait 实现项。当应用于包含其他项的项时,例如模块实现,所有子项都会继承该弃用属性。

这里有一个例子

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

pub fn bar() {}
}

RFC 包含了动机和更多细节。

must_use 属性

must_use 属性用于在值未被“使用”时发出诊断警告。

must_use 属性可以应用于用户定义的复合类型(structenumunion)、函数trait

must_use 属性可以使用 MetaNameValueStr 语法包含一条消息,例如 #[must_use = "example message"]。该消息将与警告一起给出。

当用于用户定义的复合类型时,如果表达式语句表达式具有该类型,则违反了 unused_must_use lint。

#![allow(unused)]
fn main() {
#[must_use]
struct MustUse {
    // some fields
}

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

// Violates the `unused_must_use` lint.
MustUse::new();
}

当用于函数时,如果表达式语句表达式是对该函数的调用表达式,则违反了 unused_must_use lint。

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

// Violates the unused_must_use lint.
five();
}

当用于trait 声明时,表达式语句中对返回该 trait 的impl traitdyn trait 的函数的调用表达式违反了 unused_must_use lint。

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

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

// Violates the `unused_must_use` lint.
get_critical();
}

当用于 trait 声明中的函数时,该行为也适用于调用表达式是 trait 实现中的函数的情况。

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

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

// Violates the `unused_must_use` lint.
5i32.use_me();
}

当用于 trait 实现中的函数时,该属性不起作用。

注意

包含该值的简单无操作表达式不会违反 lint。例如,将值封装在一个未实现 Drop 的类型中,然后不使用该类型,或者成为未使用的块表达式的最终表达式。

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

// None of these violate the 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 }

// Does not violate the unused_must_use lint.
let _ = five();
}

diagnostic 工具属性命名空间

#[diagnostic] 属性命名空间是用于影响编译时错误消息的属性的归属地。这些属性提供的提示不保证会被使用。

该命名空间中的未知属性会被接受,尽管它们可能会因未使用属性而发出警告。此外,已知属性的无效输入通常会是警告(详细信息请参阅属性定义)。这是为了允许将来添加或丢弃属性以及更改输入,以便进行更改而无需保持那些无意义的属性或选项继续工作。

diagnostic::on_unimplemented 属性

#[diagnostic::on_unimplemented] 属性是给编译器的提示,用于补充在需要 trait 但类型未实现该 trait 的情况下通常会生成的错误消息。

该属性应放置在trait 声明上,尽管位于其他位置不是错误。

该属性使用 MetaListNameValueStr 语法来指定其输入,尽管该属性的任何格式错误的输入都不被视为错误,以提供前向和后向兼容性。

以下键具有给定的含义

  • message — 顶层错误消息的文本。
  • label — 错误消息中损坏代码行内显示的标签文本。
  • note — 提供额外的注意事项。

note 选项可以出现多次,这将导致发出多条注意事项消息。

如果其他任何选项出现多次,则相关选项的第一次出现指定实际使用的值。随后的出现将生成警告。

对任何未知键都会生成警告。

所有这三个选项都接受一个字符串作为参数,其解释方式与 std::fmt 字符串使用相同的格式。

具有给定命名参数的格式参数将替换为以下文本

  • {Self} — 实现 trait 的类型的名称。
  • { 泛型参数名称 } — 给定泛型参数的泛型参数类型的名称。

任何其他格式参数将生成警告,但否则会按原样包含在字符串中。

无效格式字符串可能会生成警告,但其他情况下是允许的,只是可能不会按预期显示。格式说明符可能会生成警告,但其他情况下会被忽略。

在这个例子中

#[diagnostic::on_unimplemented(
    message = "My Message for `ImportantTrait<{A}>` implemented for `{Self}`",
    label = "My Label",
    note = "Note 1",
    note = "Note 2"
)]
trait ImportantTrait<A> {}

fn use_my_trait(_: impl ImportantTrait<i32>) {}

fn main() {
    use_my_trait(String::new());
}

编译器可能会生成一个看起来像这样的错误消息

error[E0277]: My Message for `ImportantTrait<i32>` implemented for `String`
  --> src/main.rs:14:18
   |
14 |     use_my_trait(String::new());
   |     ------------ ^^^^^^^^^^^^^ My Label
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `ImportantTrait<i32>` is not implemented for `String`
   = note: Note 1
   = note: Note 2

diagnostic::do_not_recommend 属性

#[diagnostic::do_not_recommend] 属性是给编译器的提示,指示不要在诊断消息中显示带注解的 trait 实现。

注意

抑制推荐在您知道该推荐通常对程序员无用时非常有用。这经常发生在使用广泛的、通用 impl 的情况下。推荐可能会将程序员引入歧途,或者 trait 实现可能是一个您不想暴露的内部细节,或者程序员可能无法满足其约束。

例如,在关于类型未实现所需 trait 的错误消息中,编译器可能会找到一个 trait 实现,如果不是该 trait 实现中的特定约束,它将满足要求。编译器可能会告诉用户存在一个 impl,但问题在于 trait 实现中的约束。#[diagnostic::do_not_recommend] 属性可用于告诉编译器不要向用户提及该 trait 实现,而只是简单地告诉用户该类型未实现所需的 trait。

该属性应放置在trait 实现项上,尽管位于其他位置不是错误。

该属性不接受任何参数,尽管意外的参数不被视为错误。

在以下示例中,有一个名为 AsExpression 的 trait,用于将任意类型转换为 SQL 库中使用的 Expression 类型。有一个名为 check 的方法,它接受一个 AsExpression

pub trait Expression {
    type SqlType;
}

pub trait AsExpression<ST> {
    type Expression: Expression<SqlType = ST>;
}

pub struct Text;
pub struct Integer;

pub struct Bound<T>(T);
pub struct SelectInt;

impl Expression for SelectInt {
    type SqlType = Integer;
}

impl<T> Expression for Bound<T> {
    type SqlType = T;
}

impl AsExpression<Integer> for i32 {
    type Expression = Bound<Integer>;
}

impl AsExpression<Text> for &'_ str {
    type Expression = Bound<Text>;
}

impl<T> Foo for T where T: Expression {}

// Uncomment this line to change the recommendation.
// #[diagnostic::do_not_recommend]
impl<T, ST> AsExpression<ST> for T
where
    T: Expression<SqlType = ST>,
{
    type Expression = T;
}

trait Foo: Expression + Sized {
    fn check<T>(&self, _: T) -> <T as AsExpression<<Self as Expression>::SqlType>>::Expression
    where
        T: AsExpression<Self::SqlType>,
    {
        todo!()
    }
}

fn main() {
    SelectInt.check("bar");
}

SelectInt 类型的 check 方法期望一个 Integer 类型。使用 i32 类型调用它会起作用,因为它被 AsExpression trait 转换为 Integer。但是,使用字符串调用则不会,并会生成一个可能看起来像这样的错误:

error[E0277]: the trait bound `&str: Expression` is not satisfied
  --> src/main.rs:53:15
   |
53 |     SelectInt.check("bar");
   |               ^^^^^ the trait `Expression` is not implemented for `&str`
   |
   = help: the following other types implement trait `Expression`:
             Bound<T>
             SelectInt
note: required for `&str` to implement `AsExpression<Integer>`
  --> src/main.rs:45:13
   |
45 | impl<T, ST> AsExpression<ST> for T
   |             ^^^^^^^^^^^^^^^^     ^
46 | where
47 |     T: Expression<SqlType = ST>,
   |        ------------------------ unsatisfied trait bound introduced here

通过将 #[diagnostic::do_not_recommend] 属性添加到 AsExpression 的通用 impl 中,消息变为:

error[E0277]: the trait bound `&str: AsExpression<Integer>` is not satisfied
  --> src/main.rs:53:15
   |
53 |     SelectInt.check("bar");
   |               ^^^^^ the trait `AsExpression<Integer>` is not implemented for `&str`
   |
   = help: the trait `AsExpression<Integer>` is not implemented for `&str`
           but trait `AsExpression<Text>` is implemented for it
   = help: for that trait implementation, expected `Text`, found `Integer`

第一个错误消息包含了一些令人困惑的信息,关于 &strExpression 的关系,以及通用 impl 中未满足的 trait 约束。在添加 #[diagnostic::do_not_recommend] 后,它不再将通用 impl 用于推荐。消息应该更清晰一些,指示字符串无法转换为 Integer