04 声明宏的基本结构与工作原理

声明宏(macro_rules!)本质上是一个基于模式匹配的代码生成器。它在编译器的语法分析阶段工作,核心流程为:拿着宏调用代码去比对预设规则,匹配成功后将规则替换为对应的代码模板。

基本语法格式:

macro_rules! 宏名称 {
    (匹配模式) => {
        生成的代码模板
    };
}

零参数宏示例:

// 定义一个不接收任何参数的宏
macro_rules! say_hello {
    () => {
        println!("Hello, Rust macros!");
    };
}

fn main() {
    say_hello!(); // 调用时必须写括号
}
  • 左侧 ()**:表示匹配模式。这里是空括号,代表该宏不接收任何参数**。

  • 右侧 { ... }**:表示匹配成功后直接拿来替换宏调用的生成代码模板**。


2. 宏如何匹配并生成代码?(执行过程)

宏的工作流程可以拆解为两个核心步骤:匹配(Matching)与 展开(Expansion)。

示例:带参数的宏

macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            println!("你调用了函数: {}", stringify!($func_name));
        }
    };
}

// 调用
create_function!(say_hello);

工作步骤

  1. 匹配(Matching):编译器拿着传入的参数 say_hello 去对照模式 ($func_name:ident)。

  • $func_name 是宏变量,用来捕获传入的参数。

  • :ident 是片段标识符,约束传入的必须是一个合法的标识符。

  • 匹配成功,$func_name 被赋值为 say_hello。

  1. 代码生成(Expansion / 替换):编译器把右侧模板中的 $func_name 整体替换为 say_hello,最终在编译前展开为标准 Rust 代码:

fn say_hello() {
    println!("你调用了函数: {}", "say_hello");
}

3. 宏的片段标识符(Fragment Specifiers)分类速查

在匹配模式中,你可以通过不同的标识符来约束传入参数的类型和结构:

  1. :expr(Expression):匹配任何合法的表达式(如 2 + 2、函数调用、字面量)。

  2. :ident(Identifier):匹配标识符(如变量名、函数名、结构体名如 x, User)。

  3. :ty(Type):匹配数据类型(见下文详解)。

  4. :pat(Pattern):匹配模式(如 match 或 let 中的 Some(x)、1..=5)。

  5. :stmt(Statement):匹配完整的语句(如 let x = 5;)。

  6. :block:匹配代码块(由大括号包裹的一系列代码)。

  7. :literal:匹配字面量(常数值,如 42、"hello"、true)。

  8. :path:匹配路径(如 std::collections::HashMap)。


4. 深入 :ty:支持内置与自定义类型

:ty 用于匹配类型,它不仅支持 Rust 官方的内置类型(如 i32, bool, f64),完全也支持自定义类型(如结构体、枚举、类型别名等)。

自定义类型匹配示例

// 1. 定义自定义结构体
#[derive(Debug)]
struct User {
    name: String,
}

// 2. 定义接收类型 ($t:ty) 和表达式 ($val:expr) 的宏
macro_rules! print_variable_info {
    ($t:ty, $val:expr) => {
        let x: $t = $val;
        println!("传入的值是: {:?}", x);
    };
}

fn main() {
    // 3. 传入自定义结构体类型 User 以及其实例化表达式
    print_variable_info!(User, User { name: "Alice".to_string() });
}
  • 原因:声明宏工作于词法/语法解析阶段,只要传入的名称符合大驼峰等语法规范,:ty 就会将其识别为合法的类型并完成代码填充。