
macro.jpeg
宏的核心概念:是什么,有什么用
首先,先帮你建立基础认知:
- 宏是什么:宏是Rust的一种"元编程"工具,它不是在运行时执行,而是在编译期展开成普通的Rust代码。你可以把它理解成"代码生成器",接收代码片段作为输入,输出新的代码。
- 宏和函数的区别:函数要求参数类型固定、数量固定;而宏可以接收任意类型、任意数量的参数,还能操作代码结构(比如生成重复的代码块)。
-
常见用途:简化重复代码(比如
vec![])、实现DSL(领域特定语言)、做编译期检查等。
Rust主要有两类宏:
-
声明式宏(Declarative Macros):用
macro_rules!定义,最常用、最易上手。 -
过程宏(Procedural Macros):更强大但复杂,分为派生宏(如
#[derive(Debug)])、属性宏、函数式宏。
一、最核心的基础语法:声明式宏(macro_rules!)
声明式宏是新手最先接触、最易掌握的,先把它的基础语法啃透,过程宏的基础语法后面再补充。
1. 定义语法(最核心模板)
声明式宏的定义有固定模板,拆成3部分看就懂了:
// 完整定义模板(逐部分解释)
macro_rules! 宏名称 { // 1. 宏定义关键字 + 宏名(命名规范:小写,和普通标识符规则一致)
(匹配模式) => { // 2. 匹配模式:定义宏被调用时的格式;=> 是固定分隔符
展开的代码块 // 3. 展开代码:宏被调用后,实际生成的Rust代码
}; // 分号结束这一组匹配规则(可以有多组匹配规则)
// 可以加更多匹配规则(处理不同调用格式)
(另一组匹配模式) => {
另一组展开代码
};
}
最简示例(无参数宏):先写一个连参数都没有的宏,理解最基础的定义和调用
// 定义:最简单的宏,无参数
macro_rules! hello {
// 匹配模式:() 表示调用时不带任何参数
() => {
// 展开代码:宏调用后,这里的代码会被替换到调用位置
println!("Hello, Rust宏!");
};
}
// 调用:宏名 + ! + 匹配模式里的内容(这里是())
fn main() {
hello!(); // 调用语法:宏名! + (匹配的内容),输出:Hello, Rust宏!
}
2. 调用语法(核心规则)
宏的调用语法只有一个核心规则:
调用格式 = 宏名 + ! + 匹配模式里的内容
(!是宏和普通函数的核心区别,必须加)
举几个不同匹配模式对应的调用例子,一看就懂:
| 宏定义的匹配模式 | 调用语法(正确) | 调用语法(错误) | 说明 |
|---|---|---|---|
() |
hello!() |
hello()(少!) |
无参数调用 |
($x:expr) |
say!(123) |
say!(123, 456)(参数多了) |
单参数调用 |
($x:expr, $y:expr) |
add!(1, 2) |
add!("a")(参数少了) |
双参数调用 |
($($x:expr),*) |
vec_like![1,2,3] |
vec_like |
可变参数调用 |
3. 带参数的基础宏(新手最常用)
用具体例子拆解“参数怎么定义”“怎么调用”,这是新手最需要的:
例子1:单参数宏(匹配单个表达式)
// 定义:带1个参数的宏(参数是任意表达式)
macro_rules! print_one {
// 匹配模式:($x:expr) 表示接收1个表达式参数,命名为$x
($x:expr) => {
// 展开代码:使用参数$x
println!("你传入的参数是:{:?}", $x);
};
}
// 调用:按匹配模式传1个表达式
fn main() {
print_one!(100); // 传入数字:输出 你传入的参数是:100
print_one!("hello"); // 传入字符串:输出 你传入的参数是:"hello"
print_one!(1+2*3); // 传入表达式:输出 你传入的参数是:7
}
例子2:多参数宏(固定数量参数)
// 定义:带2个参数的宏(两个都是表达式)
macro_rules! print_two {
// 匹配模式:($x:expr, $y:expr) 接收2个表达式,用逗号分隔
($x:expr, $y:expr) => {
println!("参数1:{:?},参数2:{:?}", $x, $y);
};
}
// 调用:传2个参数,用逗号分隔
fn main() {
print_two!(10, "rust"); // 正确调用:输出 参数1:10,参数2:"rust"
// print_two!(10); // 错误:参数数量不匹配,编译报错
}
例子3:可变参数宏(任意数量参数)
这是新手最容易懵的“重复模式”,用最简单的例子讲:
// 定义:接收任意数量参数的宏
macro_rules! print_many {
// 匹配模式:$( $x:expr ),*
// $() 包裹要重复的参数;* 表示0次/多次;, 是参数间的分隔符
($( $x:expr ),*) => {
println!("开始打印所有参数:");
// 展开代码也用 $(...)* 重复:每个参数打印一次
$(
println!("参数:{:?}", $x);
)*
};
}
// 调用:可以传0个、1个、多个参数
fn main() {
print_many!(); // 0个参数:只输出“开始打印所有参数:”
print_many!(100); // 1个参数:打印1行
print_many!(1, "a", 3.14); // 3个参数:打印3行
}
4. 关键语法符号拆解(新手必记)
| 符号/关键字 | 作用 | 例子 |
|---|---|---|
macro_rules! |
声明式宏的定义关键字(固定开头) | macro_rules! my_macro { ... } |
! |
调用宏的标志(区分宏和函数) | my_macro!() |
=> |
分隔“匹配模式”和“展开代码”(固定) | () => { println!("hi"); }; |
$ |
标记宏的参数(参数必须以$开头) |
$x:expr(参数名x,类型是表达式) |
: |
分隔参数名和“片段说明符”(指定参数类型) |
$name:ident(参数name是标识符) |
$(...)* |
重复模式:匹配/展开任意次数(0+) |
$( $x:expr ),*(匹配多个表达式) |
$(...)+ |
重复模式:匹配/展开至少1次 |
$( $x:expr ),+(必须传至少1个参数) |
二、过程宏的基础语法(新手入门级)
过程宏的定义/调用语法和声明式宏完全不同,先记“怎么用”,再懂“怎么定义”:
1. 派生宏(最常用的过程宏):调用+定义基础
(1)调用语法(新手先会用)
派生宏是加在结构体/枚举上的属性,格式:
// 调用语法:#[derive(宏名)] 加在类型定义上方
#[derive(Debug, Clone, 自定义宏名)] // 可以多个派生宏一起用
struct User {
name: String,
age: u32,
}
(2)定义语法(新手了解即可)
过程宏必须定义在库(lib) 中,且Cargo.toml要加配置,基础模板:
# Cargo.toml 必须加的配置(固定)
[lib]
proc-macro = true # 标记这是一个过程宏库
[dependencies]
# 过程宏三大基础依赖(固定)
syn = "2.0" # 解析Rust代码
quote = "1.0" # 生成Rust代码
proc-macro2 = "1.0" # 辅助处理Token
// src/lib.rs 定义派生宏的基础模板
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
// 定义派生宏的注解(固定格式:#[proc_macro_derive(宏名)])
#[proc_macro_derive(MyDerive)]
// 函数签名固定:接收TokenStream,返回TokenStream
pub fn my_derive_macro(input: TokenStream) -> TokenStream {
// 1. 解析输入的代码(比如结构体定义)
let input = parse_macro_input!(input);
// 2. 生成要展开的代码(用quote!宏)
let output = quote! {
// 这里写宏要生成的代码
impl MyDerive for YourType {
// 方法定义
}
};
// 3. 转换为TokenStream返回
TokenStream::from(output)
}
2. 属性宏(调用语法)
属性宏是自定义注解,调用语法:
// 调用语法:#[宏名(可选参数)]
#[my_attr(debug = true)] // 带参数的属性宏
fn my_function() {
// 函数体
}
#[my_attr] // 不带参数的属性宏
struct MyStruct;
三、新手必练的基础小例子(敲一遍就会)
写这3个例子,彻底掌握基础语法:
// 例子1:无参数宏
macro_rules! greet {
() => {
println!("Hello, 新手!");
};
}
// 例子2:单参数宏(打印任意类型)
macro_rules! print_val {
($val:expr) => {
println!("值:{:?},类型:{}", $val, std::any::type_name_of_val(&$val));
};
}
// 例子3:双参数宏(计算两数之和)
macro_rules! add {
($a:expr, $b:expr) => {
$a + $b // 直接返回表达式结果
};
}
fn main() {
// 调用1:无参数
greet!();
// 调用2:单参数
print_val!(123);
print_val!("abc");
// 调用3:双参数(可以直接赋值给变量)
let sum = add!(10, 20);
println!("10+20={}", sum);
}
运行结果:
Hello, 新手!
值:123,类型:i32
值:"abc",类型:&str
10+20=30
总结
- 声明式宏最基础的语法:定义用
macro_rules! 宏名 { (匹配模式) => { 展开代码 }; },调用用宏名! + 匹配模式内容,!是宏的核心标识。 - 宏参数必须以
$开头,用:指定类型(如$x:expr表示表达式参数),$(...)*处理可变数量参数。 - 过程宏调用是属性形式(
#[derive(宏名)]),定义需放在proc-macro库中,新手先会用即可。
如果敲上面的例子时遇到任何报错,或者想搞懂某个具体符号的用法,都可以问,咱们一点点拆~
全文由豆包生成