All articles
Exploring Rust Attributes in Depth
Macros

Exploring Rust Attributes in Depth

What are Attributes in Rust?

By Luis SoaresMay 29, 2023Original on Medium

What are Attributes in Rust?

In Rust, attributes are declarative tags placed above function definitions, modules, items, etc. They provide additional information or alter the behaviour of the code. Attributes in Rust start with a hashtag (#) and are placed inside square brackets ([]). For instance, an attribute could look like this: #[attribute].

Attributes are divided into three main categories: conditional compilation, crate-level attributes, and function and module-level attributes.

Conditional Compilation

Conditional compilation is controlled through attributes, allowing parts of the code to be compiled conditionally. Rust supports this through two key attributes:

  1. cfg: This attribute includes code based on a flag passed to the compiler. It can be used where an item is defined, such as functions, implementations, structs, etc.
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
    println!("You're running linux!");
}

2. cfg_attr: This attribute allows you to include other attributes based on a configuration flag conditionally.

#[cfg_attr(feature = "debug-mode", derive(Debug))]
struct Test {
    value: i32,
}

In the above code, the Debug trait is derived for the struct Test only when the debug-mode feature is enabled.

Crate-level Attributes

Crate-level attributes apply to the entire crate. They are usually placed at the top of the main file (lib.rs or main.rs). Some commonly used crate-level attributes are:

  1. crate_name: This attribute lets you set the name of the crate manually.
#![crate_name = "my_crate"]

2. crate_type: This attribute lets you define the type of crate (library, binary, etc.).

#![crate_type = "lib"]

3. deny, warn, allow, forbid: These attributes are used for handling warnings or lint checks at the crate level.

#![deny(missing_docs)]

4. macro_use: This attribute allows macros to be used from external crates.

#[macro_use]
extern crate log;

Function and Module-level Attributes

These attributes apply to functions, modules, structs, enums, traits, etc. Here are some common ones:

  1. test: This attribute marks a function as a unit test.
#[test]
fn test_addition() {
    assert_eq!(2 + 2, 4);
}

2. derive: This attribute automatically creates implementations of traits for user-defined types.

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

Practice what you learned

Reinforce this article with hands-on coding exercises and AI-powered feedback.

View all exercises

3. inline: This attribute suggests to the compiler that it should place a copy of the function's code inline to its caller, thus potentially improving runtime performance.

#[inline]
fn add(x: i32, y: i32) -> i32 {
    x + y
}

4. deprecated: This attribute can signal that an item of code should not be used and will be removed in a future version.

#[deprecated(since = "1.1.0", note = "Please use the `new_function` instead")]
fn old_function() {
    // some code
}

Attributes in the Derive Macro

The derive attribute deserves a special mention. It allows Rust to generate certain traits for a struct or enum automatically. By default, Rust can derive ten different traits:

  • Clone
  • Copy
  • Debug
  • Default
  • Eq
  • PartialEq
  • Ord
  • PartialOrd
  • Hash
  • Drop
#[derive(Debug, PartialEq, Eq)]
struct Point {
    x: i32,
    y: i32,
}

In this example, the Point struct is automatically implementing the Debug, PartialEq, and Eq traits.

Procedural Macros and Attribute-like Macros

Rust also supports procedural macros, including attribute-like macros. While not technically attributes, these behave similarly and can be used to adjust the behaviour of functions, structs, and more.

An attribute-like macro is defined like this:

#[macro_name(attributes)]

An example of this would be the popular serde library's derive macro:

#[derive(Serialize, Deserialize)]
struct Point {
    x: i32,
    y: i32,
}

In this example, Serialize and Deserialize are procedural macros that generate the necessary code to convert Point instances to and from various data formats.

Documenting your Code with Attributes

Documentation is an essential aspect of any codebase, and Rust provides built-in support for documentation through attributes:

  • doc: This attribute allows you to write documentation comments for your modules, functions, structs, enums, traits, typedefs, etc., in your code.
/// This is a documentation comment for the following struct.
#[doc = "This struct represents a point in 2D space."]
struct Point {
    x: i32,
    y: i32,
}

In this example, the doc attribute is used to generate documentation for the Point struct.

Dead Code and Unused Result Detection

Rust's attribute system can also help prevent common coding mistakes:

  • dead_code: This attribute can be used to silence warnings about code that is never called.
#[allow(dead_code)]
fn unused_function() {
    // some code
}
  • must_use: This attribute on functions will create a warning when the function's result is not used.
#[must_use]
fn function_with_important_result() -> i32 {
    // some code
    return 42;
}

Practice what you learned

Reinforce this article with hands-on coding exercises and AI-powered feedback.

View all exercises

Want to practice Rust hands-on?

Go beyond reading — solve interactive exercises with AI-powered code review on Rust Lab.