Rust Macros

Rust macros are a powerful tool for generating code at compile time, allowing you to create custom syntax extensions while writing code.

A macro is a technique for metaprogramming in code, allowing code generation at compile time. Macros help simplify code, improve code readability and maintainability, and allow developers to perform some code generation operations at compile time.

There are two types of macros in Rust: declarative macros and procedural macros.

This article mainly introduces declarative macros.

Macro Definition

In Rust, use themacro_rules!keyword to define declarative macros.

macro_rules! my_macro {
    // 模式匹配和展开
    ($arg:expr) => {
        // 生成的代码
        // 使用 $arg 来代替匹配到的表达式
    };
}

Declarative macros use themacro_rules!keyword for definition, and they are called"macro_rules"macros. This type of macro definition is based on pattern matching; it can match the structure of code and generate corresponding code based on the matched pattern. Such macros can be used to simplify common code patterns without introducing new syntax structures.

Here is an example of a simple macro definition:

Example

// Macro definition
macro_rules! greet {
    // Pattern matching
    ($name:expr) => {
        // Macro expansion
        println!("Hello, {}!", $name);
    };
}

fn main() {
    // Call the macro
    greet!("World");
}

Explanation

  • Pattern matching:Macros use pattern matching to match code fragments passed to the macro. The pattern is the left-hand side of a macro rule, used to capture different code structures.
  • Rules:Macro rules are a set of$guided patterns and corresponding expansion code, with rules separated by semicolons.
  • Macro expansion:When a macro is called, the matched pattern is replaced with the corresponding expansion code, which is the right-hand side of the macro rule.

Example

Below is a more complex example demonstrating how to use a macro to create a simplevec!macro to make it easier to create a Vec:

Example

// Macro definition
macro_rules! vec {
    // Base case: empty
    () => {
        Vec::new()
    };
   
    // Recursive case: with elements
    ($($element:expr),+ $(,)?) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($element);
            )+
            temp_vec
        }
    };
}

fn main() {
    // Call the macro
    let my_vec = vec![1, 2, 3];
    println!("{:?}", my_vec); // Output: [1, 2, 3]

    let empty_vec = vec![];
    println!("{:?}", empty_vec); // Output: []
}

In this example,vec!the macro uses pattern matching and$($element:expr),+ $(,)?)such syntax to capture the elements passed to the macro and create a Vec from them.

Note that $(,)?)is used to handle trailing commas, making it work correctly in various usage scenarios.


Procedural Macros

Procedural macros are a more flexible and powerful type of macro that allows manipulating the abstract syntax tree (AST) through custom code generation at compile time. Procedural macros are closer to functions in functionality, but they are more complex to write and use.

Types of procedural macros:

  • Derive Macros: used to automatically implement traits (such asCopy、Debug) macros.
  • Attribute Macros: used to attach additional metadata to declarations, such as#[derive(Debug)]。

The implementation of procedural macros usually requires using the functionality provided by the proc_macro library, such as TokenStream and TokenTree, to manipulate source code more directly.

Other Extensions