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);
工作步骤
匹配(Matching):编译器拿着传入的参数
say_hello去对照模式($func_name:ident)。
$func_name是宏变量,用来捕获传入的参数。:ident是片段标识符,约束传入的必须是一个合法的标识符。匹配成功,
$func_name被赋值为say_hello。
代码生成(Expansion / 替换):编译器把右侧模板中的
$func_name整体替换为say_hello,最终在编译前展开为标准 Rust 代码:
fn say_hello() {
println!("你调用了函数: {}", "say_hello");
}
3. 宏的片段标识符(Fragment Specifiers)分类速查
在匹配模式中,你可以通过不同的标识符来约束传入参数的类型和结构:
:expr(Expression):匹配任何合法的表达式(如2 + 2、函数调用、字面量)。:ident(Identifier):匹配标识符(如变量名、函数名、结构体名如x,User)。:ty(Type):匹配数据类型(见下文详解)。:pat(Pattern):匹配模式(如match或let中的Some(x)、1..=5)。:stmt(Statement):匹配完整的语句(如let x = 5;)。:block:匹配代码块(由大括号包裹的一系列代码)。:literal:匹配字面量(常数值,如42、"hello"、true)。: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就会将其识别为合法的类型并完成代码填充。