Rust宏的使用
- 开源代码
- 2025-08-06 06:42:02

两种宏的实现方式
通过标准库提供的 macro_rules! 宏实现——示范型宏通过提供编译器扩展来实现宏的分析工具
cargo install cargo-expand cargo expand 通过 macro_rules! 实现 macro_rules! add { ($a:expr, $b:expr) => { $a + $b }; } let x = add(1, 2); // 宏展开后变成 let x = 1 + 2; 宏参数可重复性* 任意次 + 一次或者多次 ? 零次或者一次
macro_rules! add { ( $($a:expr),* ) => { { let mut sum = 0; $( sum += $a; )* sum } }; } let x = add!(1, 2, 3); // 宏展开后为 let x = { let mut sum = 0; sum += 1; sum += 2; sum += 3; sum }; 递归调用添加了更多的匹配规则
第一个模式匹配规则是 () ,表示没有传入任何参数时,返回值为0 第二个模式匹配规则是 ($a:expr) ,表示只传入一个表达式时,返回该表达式的值 第三个模式匹配规则是 ($a:expr, $($rest:expr),*) ,表示传入多个表达式时,将第一个表达式与剩余的表达式进行相加操作
macro_rules! add { () => { 0 }; ($a:expr) => { $a }; ($a:expr, $($rest:expr),*) => { $a + add!($($rest),*) }; } let x = add!(1, 2, 3); // 宏展开后为 let x = 1 + 2 + 3; 过程宏格式为如下: 定义过程宏的函数将 TokenStream 作为输入 并产生 TokenStream 作为输出
use proc_macro; #[some_attribute] pub fn some_name(input: TokenStream) -> TokenStream { }分为三类
函数式宏 Function-like macros - custom!(…) 派生宏 Derive macros - #[derive(CustomDerive)] 属性宏 Attribute macros - #[CustomAttribute]
过程宏需要作为一个 crate 使用,因此可以单独新建一个工程来实现,然后在另一个工程引入
当然也可以直接在 crate 工程中调用自身的 lib, 但这会导致库的包名和目标名碰撞警告
cargo new demo cd demo && cargo new custom_macrocustom_macro/Cargo.toml
[lib] proc-macro = true 定义宏custom_macro/src/lib.rs
use proc_macro::TokenStream; #[proc_macro] pub fn make_answer(_input: TokenStream) -> TokenStream { "fn answer() -> u32 { 42 }".parse().unwrap() } 使用宏Cargo.toml
[dependencies] custom_macro={path="custom_macro"}src/main.rs
use custom_macro::make_answer; make_answer!(); // 将展开为 // fn answer() -> u32 { // 42 // } fn main() { println!("{}", answer()); } 更多示例 1. 函数式宏 // ---- custom_macro/src/lib.rs ---- use proc_macro::TokenStream; #[proc_macro] pub fn make_hello(input: TokenStream) -> TokenStream { let name = input.to_string(); let hell = "Hello ".to_string() + name.as_ref(); let fn_name = "fn hello_".to_string() + name.as_ref() + "(){ println!(\"" + hell.as_ref() + "\"); }"; fn_name.parse().unwrap() } // ---- src/main.rs ---- use custom_macro::make_hello; make_hello!(world); fn main() { hello_world(); }通过对传入的 TokenStream 进行解析,那么宏可输入的参数就可以完全自定义化,甚至自定义化为另一种语言并解析。
比如像 CSS 这类样式语法, 通过宏来解析,使得可以直接将样式写在 rust 里面
2. 派生宏派生宏可用于 struct, trait 和 enum 类型的扩展
先通过简单示例查看派生宏定义和使用以及 TokenStream 中的内容
// ---- custom_macro/src/lib.rs ---- use proc_macro::TokenStream; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { println!("{}", input.to_string()); return TokenStream::new(); } // ---- src/main.rs ---- use custom_macro::HelloMacro; #[derive(HelloMacro)] struct Person; fn main() { let _ = Person; }编译时可以看到输出的日志 struct Person ; 说明输入的 TokenStream 正是派生宏所作用的下方结构体的定义。
那么拿到这个结构体的定义后我们就可以对其进行扩展化。
使用 syn 来解析 TokenStream 中的语法, 然后使用 quote 输出代码 // ---- custom_macro/src/lib.rs ---- use proc_macro::TokenStream; use quote::quote; use syn::{parse, DeriveInput}; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { println!("{}", input.to_string()); // 解析出 DeriveInput 结构体 let ast: DeriveInput = parse(input).unwrap(); // 为该类型实现一个函数 let name = ast.ident; let gen = quote! { impl #name { fn hello_macro() { println!("Hello, Macro! My name is {}", stringify!(#name)); } } }; gen.into() } // ---- src/main.rs ---- use custom_macro::HelloMacro; #[derive(HelloMacro)] struct Person; fn main() { Person::hello_macro(); } 3. 属性宏属性宏类似于派生宏,但相比派生宏更灵活,因为你可以自定义宏属性,此外不仅可以作用于结构体枚举,还可以作用于其它项,例如函数
先通过一个简单示例了解属性宏的定义和使用
// ---- custom_macro/src/lib.rs ---- use proc_macro::TokenStream; #[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { println!("attr {} item {}", attr.to_string(), item.to_string()); TokenStream::new() } // ---- src/main.rs ---- use custom_macro::route; #[route(GET, "/")] fn index() { } fn main() { }编译输出日志 attr GET, "/" item fn index() {},可见传入的属性和宏所作用的函数项都能够在宏代码处获取。
那么拿到这些属性和函数定义本身后,我们就可以有针对性地生成代码了
// ---- custom_macro/src/lib.rs ---- use proc_macro::TokenStream; use quote::quote; #[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { println!("attr {} item {}", attr.to_string(), item.to_string()); let name = attr.to_string(); let gen = quote! { fn hello_macro() { println!("ATTR IS {}", stringify!(#name)); } }; gen.into() } // ---- src/main.rs ---- use custom_macro::route; #[route(GET, "/")] fn index() { println!("HH"); } fn main() { hello_macro(); }和派生宏不同的是属性宏的输出将会覆盖宏所作用的项, 因此再调用 index() 时将会报错,因为输出中没有定义该函数
参考文献 https://doc.rust-lang.org/reference/macros.html