宏编程-Rust

我们对宏的理解可以定义为用于生成代码的代码。编译时生成代码所以不会损耗运行性能

调用方式类似于函数,接受一个参数,但是返回的不是值,而是一堆新的代码

宏分为两大类:声明式宏( *declarative macros* ) macro_rules! 和三种过程宏( *procedural macros* ):

  • 自定义宏,#[derive]在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码
  • 类属性宏(Attribute-like macro),用于为目标添加自定义的属性
  • 类函数宏(Function-like macro),看上去就像是函数调用

宏与函数

Rust 的函数签名是固定的:定义了两个参数,就必须传入两个参数,多一个少一个都不行,而宏就可以拥有可变数量的参数

image-20250429141818328

声明式宏

声明式宏允许我们写出类似 match 的代码。match 表达式是一个控制结构,其接收一个表达式,然后将表达式的结果与多个模式进行匹配,一旦匹配了某个模式,则该模式相关联的代码将被执行

vec! 创建的动态数组支持任何元素类型,也并没有限制数组的长度,如果使用函数,我们是无法做到这一点的

1
2
3
4
5
6
7
8
9
10
11
12
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}

#[macro_export] 注释将宏进行了导出,这样其它的包就可以将该宏引入到当前作用域中,然后才能使用。

紧接着,就使用 macro_rules! 进行了宏定义,需要注意的是宏的名称是 vec,而不是 vec!,后者的感叹号只在调用时才需要。

vec 的定义结构跟 match 表达式很像,但这里我们只有一个分支,其中包含一个模式 ( $( $x:expr ),* ),跟模式相关联的代码就在 => 之后。一旦模式成功匹配,那这段相关联的代码就会替换传入的源代码。

由于 vec 宏只有一个模式,因此它只能匹配一种源代码,其它类型的都将导致报错,而更复杂的宏往往会拥有更多的分支。

美元符号明确表明这是一个宏变量而不是普通 Rust 变量

$x:expr ,匹配Rust 的任意表达式,并将该表达式命名为 $x

逗号说明一个可有可无的逗号分隔符可以出现在 $() 所匹配的代码之后。

* 说明该模式匹配零个或更多个 * 之前的任何模式

模式匹配后的代码

1
2
3
4
5
6
7
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}

过程宏

从属性生成代码

过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。

自定义 derive

创建一个 hello_macro crate,

1、定义一个trait名为HelloMacro。

2、定义一个关联函数hello_macro。

不同于让用户为其每一个类型实现 HelloMacro trait,我们将会提供一个过程式宏以便用户可以使用 #[derive(HelloMacro)] 注解它们的类型来得到 hello_macro 函数的默认实现。

该默认实现会打印 Hello, Macro! My name is TypeName!,其中 TypeName 为定义了 trait 的类型名。

命名规范,我们项目名称为hello_macro,那么它的自定义派生宏名称为hello_macro_derive

需要在当前项目下创建自定义宏项目的create

image-20250429151529355

宏create,lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn;
use syn::DeriveInput;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// 基于 input 构建 AST 语法树
let ast:DeriveInput = syn::parse(input).unwrap();

// 构建特征实现代码
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen1 = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen1.into()
}

宏create,Cargo.toml

1
2
3
4
5
6
7
8
9
10
11
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2024"

[dependencies]
syn="2.0"
quote = "1.0"

[lib]
proc-macro = true

新建二进制create测试宏

image-20250429154528610

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Sunfei;

#[derive(HelloMacro)]
struct Sunface;

fn main() {
Sunfei::hello_macro();
Sunface::hello_macro();
}

image-20250429155035498

类属性宏(Attribute-like macro)

类函数宏(Function-like macro)