Attributes

Mark an Item as must use
Mark an Item as deprecated
Control Compilation Diagnostic Messages with Lint Check Attributes
Suppress Irrelevant Warnings during Early Development
Enforce Good Practices and Catch Issues Early with Lint Attributes
Compile Code Conditionally
Conditionally Compile Code Blocks with cfg-if

Attribute Syntax

Rust by example - attributes

Attributes are annotations you attach to your Rust code, like functions, structs, modules, or even entire crates. They provide extra information or instructions to the Rust compiler or other tools (like linters or documentation generators). They don't change the logic of the code directly, but they influence how it's compiled, checked, or processed.

Common attributes include:

  • #[derive(...)] to autogenerate an implementation of certain traits.
  • #[test] to mark functions as tests.
  • #[allow(...)], #[warn(...)], #[deny(...)] to control compiler lints (warnings/errors).
  • #[deprecated] to mark items as outdated.
  • #[must_use] to warn if the result of a function isn't used.
  • #[cfg(...)] for conditional compilation.

The following is an example of #[derive(...)]:

// This attribute tells the compiler to automatically generate
// an implementation of the `std::fmt::Debug` trait for the `Point` struct.
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 10, y: 20 };

    // Because we derived `Debug`, we can print the struct using the `{:?}` format specifier.
    // Without `#[derive(Debug)]`, this line would cause a compile-time error.
    println!("The point is: {:?}", p1);
}

Attribute can take arguments with different syntaxes:

#[attribute = "value"]
#[attribute(key = "value")]
#[attribute(value)]
#[attribute(value, value2)]

Outer attributes #[attr] apply to the item below them. Inner attributes #![attr] apply to the item that the attribute is declared within. You often see these at the very top of a file (or inline module) to apply to the entire module, or at the very top of lib.rs or main.rs to apply to the entire crate.

Mark an Item as must use

Apply the #[must_use] attribute to functions, methods, or entire types (like structs or enums) to signal that their return value is important and shouldn't be ignored.

/// The `#[must_use]` attribute indicates that the return value of a function
/// must be used. This attribute can also be applied to traits, structs, enums,
/// etc.
#[must_use]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    // Use the return value of the function.
    println!("{}", add(1, 2));

    // Uncomment the following to see the warning:
    // "unused return value of `attributes_must_use::add` that must be used"
    // WARNING: add(2, 3);
}

Functions that return Result<T, E> are often #[must_use]. If you call such a function and ignore the Result, you're potentially ignoring an error that occurred and could be corrected or reported.

In the builder pattern, methods often return Self or a modified version of the builder. The final .build() or similar method returns the constructed object. If you forget to call the final method or assign the result, you haven't actually created the object you intended. #[must_use] on the builder type or its methods helps catch this.

Mark an Item as deprecated

When the Rust compiler sees code using an item marked as #[deprecated], it will issue a warning during compilation.

#![allow(deprecated)]

// Mark a function as deprecated.
// That also works for structs, enums, etc...
#[deprecated(since = "5.2.0", note = "Use `bar` instead")]
pub fn foo() {
    println!("foo");
}

fn main() {
    // Use of a deprecated item.
    // Normally, we would get a warning abou using a deprecated function.
    foo();
    // In this case, the module-wide `#![allow(deprecated)]` attribute
    // (first line of the example) suppresses the warning.
}

Control Compilation Diagnostic Messages with Lint Check Attributes

The following attributes are used for controlling diagnostic messages (lints) during compilation. Replace ... by the lint name e.g., dead_code to detects unused, unexported items.

  • #[allow(...)] overrides the check, so that violations will go unreported.
  • #[expect(...)] indicates that lint is expected to be emitted.
  • #[warn(...)] warns about violations but continues compilation.
  • #[deny(...)] signals an error after encountering a violation,
  • #[forbid(...)] is the same as deny, but also forbids changing the lint level afterwards.

To print the list of compiler 'lint' options and default settings, enter rustc -W help at the command prompt. rustc⮳ also recognizes the tool lints for "clippy" and "rustdoc" e.g. #![warn(clippy::pedantic)].

You can apply lint attributes to specific items (functions, structs, etc.) or to entire modules:

// Disables the `dead_code` lint for the entire file.
#![allow(dead_code)]

/// This function is defined but never called.
///
/// The attribute above disables the `dead_code` lint,
/// which would otherwise warn about this unused function.
///
/// You could also add a `#[allow(dead_code)]` attribute here
/// to disable the lint for this function only.
fn unused_function() {}

fn main() {
    println!("Nobody is calling `unused_function`.");
}

See also Lints (rustc book)⮳.

Suppress Irrelevant Warnings during Early Development

During early development, consider placing the following lint attributes at the top of your main.rs or lib.rs.

//! `allow` attributes suppress specific warnings.
//!
//! These attributes are typically used during eatly development to temporarily
//! ignore warnings that are not relevant at the moment, but should be
//! addressed before releasing the code.
#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(unused_imports)]
#![allow(unused_must_use)]
// Or simply use: #![allow(unused)]
#![allow(dead_code)]
#![allow(missing_docs)]

// This import is not used anywhere:
use std::thread;

// This struct is public but is not documented:
pub struct S;

/// This function is defined but never called.
fn dead_code() {}

/// The return value of this function should be used.
#[must_use]
fn required() -> u32 {
    42
}

fn main() {
    // This variable is defined but not used.
    let x = 1;
    // This mutable variable is defined but not used.
    let mut m = 2;
    // The return value of this function is not used, despite the `#[must_use]`
    // attribute above.
    required();
    println!("Done!");
}

Enforce Good Practices and Catch Issues Early with Lint Attributes

For production-ready code, replace the above by the following (or similar):

//! Typical lint attributes for production code.
//!
//! The following is a set of attributes commonly used in production
//! code to enforce good practices and catch potential issues early.

// The `warn` attribute creates compiler warnings in case of violation.
#![warn(unused, missing_debug_implementations, missing_docs, rust_2018_idioms)]
// You may also add `missing_copy_implementations` if desirable.
// It detects potentially-forgotten implementations of `Copy` for public types.

// The `deny` attribute creates an error in case of violation.
#![deny(unreachable_pub)]
// The following prohibits unsafe blocks / functions.
// `forbid` is the same as `deny`, but also forbids changing the lint level
// afterwards.
#![forbid(unsafe_code)]

// Uncomment the following to observe compiler warnings or errors:

// WARNING: fn dead_code() { println!("This function is not used!"); }

// ERROR: unsafe fn unsafe_func() {
// println!("This function is marked as unsafe.");
// }

// ERROR:
// fn contains_a_unsafe_block() {
//     unsafe {
//     }
// }

/// This is the required documentation for `S`.
/// We also had to derive `Debug` to avoid a warning.
#[derive(Debug)]
pub(crate) struct S;

/// Here is the required documentation
/// for the main function.
fn main() {
    let s = S;
    println!("{:?}", s);
}

Compile Code Conditionally

Conditional compilation includes or excludes specific pieces of your code based on conditions that are checked at compile time. Use the #[cfg(...)] attribute to write code that only compiles for a specific operating system or architecture (like x86_64, ARM).

//! This example demonstrates conditional compilation using the `#[cfg]`
//! attribute and `cfg!` macro.
//!
//! Conditional compilation allows you to include or exclude code based on
//! certain conditions, such as the target operating system, architecture,
//! or enabled features.

/// This function only gets compiled
/// if the target OS is Linux.
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
    println!("You are running Linux!");
}

/// This function only gets compiled
/// if the target OS is *not* Linux.
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
    println!("You are *not* running Linux!");
}

fn main() {
    are_you_on_linux();

    println!("Are you sure?");
    // Alternative to `#[cfg(...)]`: use `cfg!(...)`.
    if cfg!(target_os = "linux") {
        println!("Yes. It's definitely Linux!");
    } else {
        println!("Yes. It's definitely *not* Linux!");
    }
}

See also Conditional compilation⮳.

Conditionally Compile Code Blocks with cfg-if

cfg-if cfg-if-crates.io cfg-if-github cfg-if-lib.rs

cfg-if is a macro to ergonomically define an item depending on a large number of #[cfg] parameters. It is structured like an "if-else" chain. The first matching branch is the item that gets emitted.

use cfg_if::cfg_if;

/// This example demonstrates how to use `cfg-if` to conditionally compile
/// code blocks.
///
/// The `cfg-if` crate provides a convenient way to handle conditional
/// compilation based on configuration flags. This is particularly useful
/// when you need to write code that behaves differently depending on the
/// target platform, enabled features, or other build-time configurations.
///
/// First, add the dependency to your Cargo.toml:
/// ```toml
/// [dependencies]
/// cfg-if = "1.0.0"
/// ```
fn main() {
    // Basic usage example:
    cfg_if! {
        if #[cfg(target_os = "windows")] {
            println!("Running on Windows!");
        } else if #[cfg(target_os = "macos")] {
            println!("Running on macOS!");
        } else if #[cfg(target_os = "linux")] {
            println!("Running on Linux!");
        } else {
            println!("Running on an unknown platform!");
        }
    }

    // Another example with feature flags:
    cfg_if! {
        if #[cfg(feature = "full")] {
            fn get_functionality() -> &'static str {
                "Full functionality enabled"
            }
        } else {
            fn get_functionality() -> &'static str {
                "Limited functionality only"
            }
        }
    }

    println!("Functionality: {}", get_functionality());

    // Nested example.
    cfg_if! {
        if #[cfg(target_arch = "x86_64")] {
            cfg_if! {
                if #[cfg(target_feature = "avx2")] {
                    println!("Using AVX2 optimized routines");
                } else if #[cfg(target_feature = "sse2")] {
                    println!("Using SSE2 optimized routines");
                } else {
                    println!("Using standard routines");
                }
            }
        } else {
            println!("Architecture-specific optimizations not available");
        }
    }
}

Automatically Derive Common Traits

See Automatic derivation.

Related Topics

  • Derive.
  • Rust Patterns.
  • Testing.

References