» Rust快速入门 » 2. 高级篇 » 2.5 macro_rules! 宏

macro_rules! 宏规则

Rust提供了一个强大的宏系统,允许元编程。 宏看起来像函数,但它们的名称都以感叹号 ! 结尾。

// 定义一个名为 `print_message` 的宏
macro_rules! print_message {
    // 此模式匹配简单消息并打印它
    // `($message:expr)` 表示宏接受一个表达式参数。
    ($message:expr) => {
        // 该宏会将此块的内容替换到宏调用处。
        println!("Message: {}", $message);
    };
}

fn main() {
    // 使用自定义宏打印简单消息
    print_message!("Hello, Rust!");
    // Message: Hello, Rust!
}

语法

Designators (指示符)

宏的参数以美元符号 $ 开头,并使用指示符进行类型注解。

这是一些可用指示符的示例:

  • block 表示块
  • expr 表示表达式
  • ident 表示标识符(变量/函数名)
  • literal 表示字面常量
  • stmt 表示语句
macro_rules! print_result {
    // 此宏接受类型为 `expr` 的表达式并将其作为字符串与其结果一起打印出来。
    // 使用 `expr` 指示符表示表达式。
    ($expression:expr) => {
        // `stringify!` 将表达式按原样转换为字符串。
        println!("{:?} = {:?}",
                 stringify!($expression),
                 $expression);
    };
}

fn main() {
    print_result!(58u32 + 58);
    // "58u32 + 58" = 116

    print_result!({
        let x = 27u32;
        x * x - 1
    });
    // "{ let x = 27u32; x * x - 1 }" = 728
}

可以在 Rust参考手册 中了解更多关于指示符的信息。

重载

宏可以被重载,以接受不同的参数组合。

macro_rules! print_message {
    // 此模式匹配简单消息并打印它
    ($message:expr) => {
        println!("Message: {}", $message);
    };

    // 重载!
    // 此模式匹配具有 format 格式字符串及其参数的消息并打印它
    ($format:expr, $($arg:expr),*) => {
        println!($format, $($arg),*);
    };
}

fn main() {
    // 使用自定义宏打印简单消息
    print_message!("Hello, Rust!");
    // Message: Hello, Rust!

    // 使用带有格式字符串和参数的自定义宏
    print_message!("{} + {} = {}", 2, 3, 2 + 3);
    // 2 + 3 = 5
}

重复

宏可以在参数列表中使用 + 表示一个参数可重复一次或多次,或 * 表示参数可以出现任意次。

因此,在上面示例中的匹配器 $($arg:expr),* 意味着它将匹配任意个由逗号分隔的表达式。

DSL

DSL = 领域特定语言

DSL 是嵌入在 Rust 宏中的迷你"语言"。它属于完全合法的真 Rust,因为宏系统会将其展开为正常的 Rust 结构,但它直观看起来就像一种小语言。

macro_rules! calculate {
    (eval $e:expr) => {
        {
            let val: usize = $e;
            // `stringify!` 将表达式按原样转换为字符串。
            println!("{} = {}", stringify!{$e}, val);
        }
    };
}

fn main() {
    calculate! {
        eval 5 + 8 // `eval` 不是 Rust 关键字,但它看起来像是!这就是"DSL"。
    }
    // 5 + 8 = 13

    calculate! {
        eval (5 + 8) * (8 / 5)
    }
    // (5 + 8) * (8 / 5) = 13
}

变参

变参 接口接受任意数量的参数。

// 定义一个名为 `print_args` 的变参宏
macro_rules! print_args {
    // 基本 case:没有参数
    () => {};

    // 递归 case:打印第一个参数,并为剩余参数再次调用宏
    ($first:expr $(, $rest:expr)*) => {
        println!("Argument: {}", $first);
        print_args!($($rest),*);
    };
}

fn main() {
    // 使用不同数量的参数使用变参宏
    print_args!();                  
    // No output
    print_args!(1);                 
    // Argument: 1
    print_args!(10, "literank", 3.14);
    // Argument: 10, Argument: literank, Argument: 3.14
}

代码挑战

尝试修改编辑器中提供的宏以获取正确的答案。

Loading...
> 此处输出代码运行结果
上页
下页