Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Диагностические атрибуты

Следующие атрибуты используются для управления или генерации диагностических сообщений во время компиляции.

Атрибуты проверки линтов (lint)

Проверка линта определяет потенциально нежелательный шаблон кодирования, такой как недостижимый код или пропущенная документация.

Атрибуты линтов allow, expect, warn, deny и forbid используют синтаксис MetaListPaths для указания списка имён линтов, чтобы изменить уровень линта для сущности, к которой применяется атрибут.

Для любой проверки линта C:

  • #[allow(C)] отключает проверку для C, чтобы нарушения не сообщались.
  • #[expect(C)] указывает, что ожидается срабатывание линта C. Атрибут подавит срабатывание C или выдаст предупреждение, если ожидание не выполнено.
  • #[warn(C)] предупреждает о нарушениях C, но продолжает компиляцию.
  • #[deny(C)] сигнализирует об ошибке после обнаружения нарушения C.
  • #[forbid(C)] аналогичен deny(C), но также запрещает изменение уровня линта впоследствии.

Note

Проверки линтов, поддерживаемые rustc, можно найти с помощью rustc -W help, вместе с их настройками по умолчанию, и они документированы в книге rustc.

#![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 }
}
}

Атрибуты линтов могут переопределять уровень, указанный в предыдущем атрибуте, до тех пор, пока уровень не пытается изменить запрещённый линт (за исключением deny, который разрешён внутри контекста forbid, но игнорируется). Предыдущие атрибуты — это атрибуты из более высокого уровня в синтаксическом дереве или из предыдущего атрибута на той же сущности, перечисленные в порядке следования исходного кода слева направо.

Этот пример показывает, как можно использовать allow и warn для включения и выключения конкретной проверки:

#![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 для запрета использования allow или expect для этой проверки линта:

#![allow(unused)]
fn main() {
#[forbid(missing_docs)]
pub mod m3 {
    // Попытка изменить уровень предупреждения вызывает ошибку здесь
    #[allow(missing_docs)]
    /// Returns 2.
    pub fn undocumented_too() -> i32 { 2 }
}
}

Note

rustc позволяет устанавливать уровни линтов через командную строку, а также поддерживает установку пределов для сообщаемых линтов.

Причины линтов

Все атрибуты линтов поддерживают дополнительный параметр reason, чтобы предоставить контекст, почему был добавлен определённый атрибут. Эта причина будет отображена как часть сообщения линта, если линт сработает на заданном уровне.

#![allow(unused)]
fn main() {
// `keyword_idents` разрешён по умолчанию. Здесь мы запрещаем его,
// чтобы избежать миграции идентификаторов при обновлении редакции.
#![deny(
    keyword_idents,
    reason = "мы хотим избежать этих идентификаторов для совместимости с будущим"
)]

// Это имя было разрешено в редакции Rust 2015. Мы всё ещё стремимся избегать
// этого для совместимости с будущим и чтобы не путать конечных пользователей.
fn dyn() {}
}

Вот другой пример, где линт разрешён с причиной:

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

pub fn get_path() -> PathBuf {
    // Параметр `reason` в атрибутах `allow` служит документацией для читателя.
    #[allow(unused_mut, reason = "это изменяется только на некоторых платформах")]
    let mut file_name = PathBuf::from("git");

    #[cfg(target_os = "windows")]
    file_name.set_extension("exe");

    file_name
}
}

Атрибут #[expect]

Атрибут #[expect(C)] создаёт ожидание для линта C. Ожидание будет выполнено, если атрибут #[warn(C)] в том же месте привёл бы к срабатыванию линта. Если ожидание не выполнено, потому что линт C не сработал, линт unfulfilled_lint_expectations будет выдан на этом атрибуте.

fn main() {
    // Этот атрибут `#[expect]` создаёт ожидание, что линт `unused_variables`
    // сработает на следующем операторе. Это ожидание не выполнено, так как
    // переменная `question` используется макросом `println!`.
    // Поэтому линт `unfulfilled_lint_expectations` будет выдан на атрибуте.
    #[expect(unused_variables)]
    let question = "who lives in a pineapple under the sea?";
    println!("{question}");

    // Этот атрибут `#[expect]` создаёт ожидание, которое будет выполнено, так как
    // переменная `answer` никогда не используется. Линт `unused_variables`, который обычно
    // сработал бы, подавлен. Никакого предупреждения для оператора или атрибута не будет.
    #[expect(unused_variables)]
    let answer = "SpongeBob SquarePants!";
}

Ожидание линта выполняется только срабатываниями линтов, которые были подавлены атрибутом expect. Если уровень линта изменён в области видимости другими атрибутами уровня, такими как allow или warn, срабатывание линта будет обработано соответствующим образом, и ожидание останется невыполненным.

#![allow(unused)]
fn main() {
#[expect(unused_variables)]
fn select_song() {
    // Это вызовет линт `unused_variables` на уровне предупреждения,
    // как определено атрибутом `warn`. Это не выполнит ожидание выше функции.
    #[warn(unused_variables)]
    let song_name = "Crab Rave";

    // Атрибут `allow` подавляет срабатывание линта. Это не выполнит
    // ожидание, так как оно было подавлено атрибутом `allow`,
    // а не атрибутом `expect` выше функции.
    #[allow(unused_variables)]
    let song_creator = "Noisestorm";

    // Этот атрибут `expect` подавит срабатывание линта `unused_variables`
    // на переменной. Атрибут `expect` выше функции всё равно не будет
    // выполнен, так как это срабатывание линта было подавлено локальным
    // атрибутом expect.
    #[expect(unused_variables)]
    let song_version = "Monstercat Release";
}
}

Если атрибут expect содержит несколько линтов, каждый из них ожидается отдельно. Для группы линтов достаточно, чтобы один линт внутри группы сработал:

#![allow(unused)]
fn main() {
// Это ожидание будет выполнено неиспользуемым значением внутри функции,
// так как сработавший линт `unused_variables` находится внутри группы линтов `unused`.
#[expect(unused)]
pub fn thoughts() {
    let unused = "I'm running out of examples";
}

pub fn another_example() {
    // Этот атрибут создаёт два ожидания линта. Линт `unused_mut` будет
    // подавлен и таким образом выполнит первое ожидание. Линт `unused_variables`
    // не сработает, так как переменная используется. Это ожидание поэтому
    // останется неудовлетворённым, и будет выдано предупреждение.
    #[expect(unused_mut, unused_variables)]
    let mut link = "https://www.rust-lang.org/";

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

Note

Поведение #[expect(unfulfilled_lint_expectations)] в настоящее время определено так, что оно всегда генерирует линт unfulfilled_lint_expectations.

Группы линтов

Линты могут быть организованы в именованные группы, чтобы уровень связанных линтов можно было настраивать вместе. Использование именованной группы эквивалентно перечислению линтов внутри этой группы.

#![allow(unused)]
fn main() {
// Это разрешает все линты в группе "unused".
#[allow(unused)]
// Это переопределяет линт "unused_must_use" из группы "unused" на запрет.
#[deny(unused_must_use)]
fn example() {
    // Это не генерирует предупреждение, потому что линт "unused_variables"
    // находится в группе "unused".
    let x = 1;
    // Это генерирует ошибку, потому что результат не используется и
    // "unused_must_use" помечен как "deny".
    std::fs::remove_file("some_file"); // ERROR: неиспользованный `Result`, который должен быть использован
}
}

Существует специальная группа с именем “warnings”, которая включает все линты на уровне “warn”. Группа “warnings” игнорирует порядок атрибутов и применяется ко всем линтам, которые иначе выдали бы предупреждение внутри сущности.

#![allow(unused)]
fn main() {
unsafe fn an_unsafe_fn() {}
// Порядок этих двух атрибутов не имеет значения.
#[deny(warnings)]
// Линт unsafe_code обычно разрешён ("allow") по умолчанию.
#[warn(unsafe_code)]
fn example_err() {
    // Это ошибка, потому что предупреждение `unsafe_code` было
    // повышено до "deny".
    unsafe { an_unsafe_fn() } // ERROR: использование `unsafe` блока
}
}

Атрибуты линтов инструментов

Линты инструментов позволяют использовать области видимости для линтов, чтобы allow, warn, deny или forbid линты определённых инструментов.

Линты инструментов проверяются только тогда, когда связанный инструмент активен. Если атрибут линта, такой как allow, ссылается на несуществующий линт инструмента, компилятор не будет предупреждать о несуществующем линте, пока вы не используете инструмент.

В остальном они работают так же, как обычные атрибуты линтов:

// установить всю группу линтов `pedantic` clippy на уровень warn
#![warn(clippy::pedantic)]
// отключить предупреждения от линта `filter_map` clippy
#![allow(clippy::filter_map)]

fn main() {
    // ...
}

// отключить линт `cmp_nan` clippy только для этой функции
#[allow(clippy::cmp_nan)]
fn foo() {
    // ...
}

Note

rustc в настоящее время распознаёт линты инструментов для “clippy” и “rustdoc”.

Атрибут deprecated

Атрибут deprecated помечает элемент как устаревший. rustc будет выдавать предупреждения при использовании элементов с #[deprecated]. rustdoc будет показывать устаревание элемента, включая версию since и note, если они доступны.

Атрибут deprecated имеет несколько форм:

  • deprecated — Выдаёт общее сообщение.
  • deprecated = "message" — Включает данную строку в сообщение об устаревании.
  • Синтаксис MetaListNameValueStr с двумя необязательными полями:
    • since — Указывает номер версии, когда элемент был устаревшим. rustc в настоящее время не интерпретирует строку, но внешние инструменты, такие как Clippy, могут проверять корректность значения.
    • note — Указывает строку, которая должна быть включена в сообщение об устаревании. Обычно используется для предоставления объяснения об устаревании и предпочтительных альтернатив.

Атрибут deprecated может применяться к любому элементу, элементу трейта, варианту перечисления, полю структуры, элементу внешнего блока или определению макроса. Он не может быть применён к элементам реализации трейта. При применении к элементу, содержащему другие элементы, такие как модуль или реализация, все дочерние элементы наследуют атрибут устаревания.

Вот пример:

#![allow(unused)]
fn main() {
#[deprecated(since = "5.2.0", note = "foo редко использовался. Пользователям следует вместо этого использовать bar")]
pub fn foo() {}

pub fn bar() {}
}

RFC содержит обоснования и более подробную информацию.

Атрибут must_use

Атрибут must_use используется для выдачи диагностического предупреждения, когда значение не “используется”.

Атрибут must_use может применяться к пользовательским составным типам (struct, enum и union), функциям и трейтам.

Атрибут must_use может включать сообщение, используя синтаксис MetaNameValueStr, такой как #[must_use = "example message"]. Сообщение будет выдано вместе с предупреждением.

При использовании на пользовательских составных типах, если выражение выражения-оператора имеет этот тип, то нарушается линт unused_must_use.

#![allow(unused)]
fn main() {
#[must_use]
struct MustUse {
    // некоторые поля
}

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

// Нарушает линт `unused_must_use`.
MustUse::new();
}

При использовании на функции, если выражение выражения-оператора является вызовом выражения этой функции, то нарушается линт unused_must_use.

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

// Нарушает линт unused_must_use.
five();
}

При использовании на объявлении трейта, вызов выражения выражения-оператора функции, которая возвращает impl trait или dyn trait этого трейта, нарушает линт unused_must_use.

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

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

// Нарушает линт `unused_must_use`.
get_critical();
}

При использовании на функции в объявлении трейта, это поведение также применяется, когда вызов выражения является функцией из реализации трейта.

#![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`.
5i32.use_me();
}

При использовании на функции в реализации трейта, атрибут ничего не делает.

Note

Тривиальные неточные выражения, содержащие значение, не нарушают линт. Примеры включают обёртывание значения в тип, который не реализует Drop, а затем неиспользование этого типа и быть конечным выражением блочного выражения, которое не используется.

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

// Ни одно из этих действий не нарушает линт unused_must_use.
(five(),);
Some(five());
{ five() };
if true { five() } else { 0i32 };
match true {
    _ => five()
};
}

Note

Идиоматично использовать let оператор с шаблоном _, когда намеренно отбрасывается must-use значение.

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

// Не нарушает линт unused_must_use.
let _ = five();
}

Пространство имён атрибутов инструмента diagnostic

Пространство имён атрибута #[diagnostic] — это место для атрибутов, влияющих на сообщения об ошибках во время компиляции. Подсказки, предоставляемые этими атрибутами, не гарантированно используются.

Неизвестные атрибуты в этом пространстве имён принимаются, хотя они могут выдавать предупреждения о неиспользуемых атрибутах. Кроме того, неверные входные данные для известных атрибутов обычно приводят к предупреждению (см. определения атрибутов для подробностей). Это предназначено для允许 добавления или удаления атрибутов и изменения входных данных в будущем, чтобы允许 изменения без необходимости сохранять незначащие атрибуты или опции работающими.

Атрибут diagnostic::on_unimplemented

Атрибут #[diagnostic::on_unimplemented] — это подсказка компилятору дополнить сообщение об ошибке, которое обычно генерируется в сценариях, где требуется трейт, но он не реализован для типа.

Атрибут должен размещаться на объявлении трейта, хотя его размещение в других позициях не является ошибкой.

Атрибут использует синтаксис MetaListNameValueStr для указания своих входных данных, хотя любые некорректные входные данные для атрибута не считаются ошибкой для обеспечения прямой и обратной совместимости.

Следующие ключи имеют указанное значение:

  • message — Текст для основного сообщения об ошибке.
  • label — Текст для метки, показываемой встроенно в сломанном коде в сообщении об ошибке.
  • note — Предоставляет дополнительные примечания.

Опция note может появляться несколько раз, что приводит к выдаче нескольких сообщений-примечаний.

Если любая другая опция появляется несколько раз, первое вхождение соответствующей опции определяет фактически используемое значение. Последующие вхождения генерируют предупреждение.

Предупреждение генерируется для любых неизвестных ключей.

Все три опции принимают строку в качестве аргумента, интерпретируемую с использованием того же форматирования, что и строка std::fmt.

Параметры форматирования с данным именованным параметром будут заменены следующим текстом:

  • {Self} — Имя типа, реализующего трейт.
  • { ИмяПараметраДженерика } — Имя типа аргумента дженерика для данного параметра дженерика.

Любой другой параметр форматирования сгенерирует предупреждение, но в остальном будет включён в строку как есть.

Некорректные строки форматирования могут генерировать предупреждение, но в остальном разрешены, хотя могут отображаться не так, как задумано. Спецификаторы форматирования могут генерировать предупреждение, но в остальном игнорируются.

В этом примере:

#[diagnostic::on_unimplemented(
    message = "Моё сообщение для `ImportantTrait<{A}>`, реализованного для `{Self}`",
    label = "Моя метка",
    note = "Примечание 1",
    note = "Примечание 2"
)]
trait ImportantTrait<A> {}

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

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

компилятор может сгенерировать сообщение об ошибке, которое выглядит так:

error[E0277]: Моё сообщение для `ImportantTrait<i32>`, реализованного для `String`
  --> src/main.rs:14:18
   |
14 |     use_my_trait(String::new());
   |     ------------ ^^^^^^^^^^^^^ Моя метка
   |     |
   |     required by a bound introduced by this call
   |
   = help: трейт `ImportantTrait<i32>` не реализован для `String`
   = note: Примечание 1
   = note: Примечание 2

Атрибут diagnostic::do_not_recommend

Атрибут #[diagnostic::do_not_recommend] — это подсказка компилятору не показывать аннотированную реализацию трейта как часть диагностического сообщения.

Note

Подавление рекомендации может быть полезным, если вы знаете, что рекомендация обычно не будет полезна программисту. Это часто происходит с широкими, общими реализациями (blanket impls). Рекомендация может направить программиста по ложному пути, или реализация трейта может быть внутренней деталью, которую вы не хотите раскрывать, или ограничения (bounds) могут быть невыполнимыми для программиста.

Например, в сообщении об ошибке о том, что тип не реализует требуемый трейт, компилятор может найти реализацию трейта, которая удовлетворила бы требованиям, если бы не определённые ограничения в реализации трейта. Компилятор может сказать пользователю, что есть impl, но проблема в ограничениях реализации трейта. Атрибут #[diagnostic::do_not_recommend] можно использовать, чтобы сказать компилятору не сообщать пользователю о реализации трейта, а вместо этого просто сказать, что тип не реализует требуемый трейт.

Атрибут должен размещаться на элементе реализации трейта, хотя его размещение в других позициях не является ошибкой.

Атрибут не принимает никаких аргументов, хотя неожиданные аргументы не считаются ошибкой.

В следующем примере есть трейт AsExpression, который используется для приведения произвольных типов к типу Expression, используемому в SQL-библиотеке. Есть метод 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 {}

// Раскомментируйте эту строку, чтобы изменить рекомендацию.
// #[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");
}

Метод check типа SelectInt ожидает тип Integer. Вызов его с типом i32 работает, так как он преобразуется в Integer трейтом AsExpression. Однако вызов его со строкой не работает и генерирует ошибку, которая может выглядеть так:

error[E0277]: трейт-ограничение `&str: Expression` не выполнено
  --> src/main.rs:53:15
   |
53 |     SelectInt.check("bar");
   |               ^^^^^ трейт `Expression` не реализован для `&str`
   |
   = help: следующие другие типы реализуют трейт `Expression`:
             Bound<T>
             SelectInt
note: требуется для `&str`, чтобы реализовать `AsExpression<Integer>`
  --> src/main.rs:45:13
   |
45 | impl<T, ST> AsExpression<ST> for T
   |             ^^^^^^^^^^^^^^^^     ^
46 | where
47 |     T: Expression<SqlType = ST>,
   |        ------------------------ невыполненное трейт-ограничение, введённое здесь

Добавив атрибут #[diagnostic::do_not_recommend] к обобщённой impl для AsExpression, сообщение изменяется на:

error[E0277]: трейт-ограничение `&str: AsExpression<Integer>` не выполнено
  --> src/main.rs:53:15
   |
53 |     SelectInt.check("bar");
   |               ^^^^^ трейт `AsExpression<Integer>` не реализован для `&str`
   |
   = help: трейт `AsExpression<Integer>` не реализован для `&str`
           но трейт `AsExpression<Text>` реализован для него
   = help: для этой реализации трейта ожидался `Text`, но найден `Integer`

Первое сообщение об ошибке включает несколько сбивающее с толку сообщение об ошибке об отношении &str и Expression, а также невыполненное трейт-ограничение в обобщённой impl. После добавления #[diagnostic::do_not_recommend] оно больше не рассматривает обобщённую impl для рекомендации. Сообщение должно быть немного яснее, с указанием, что строку нельзя преобразовать в Integer.