rust:第十六章宏
目录
第十六章 宏
宏(Macro)是Rust中一种强大的元编程工具,允许你编写生成代码的代码。Rust有两种类型的宏:声明宏(declarative macros)和过程宏(procedural macros)。本章将详细介绍这两种宏的工作原理和使用方法。
1. 宏概述
1.1 什么是宏
宏是一种代码生成机制,在编译时展开为实际的Rust代码。与函数不同,宏可以:
- 接收可变数量的参数
- 在编译时生成代码
- 操作Rust的抽象语法树(AST)
1.2 宏与函数的区别
| 特性 | 函数 | 宏 |
| 参数数量 | 固定 | 可变 |
| 执行时机 | 运行时 | 编译时 |
| 类型检查 | 严格 | 模式匹配 |
| 代码生成 | 否 | 是 |
// 函数 - 固定参数 fn add(a: i32, b: i32) -> i32 { a + b } // 宏 - 可变参数 println!("{} {}", 1, 2); println!("{} {} {}", 1, 2, 3); vec![1, 2, 3, 4, 5]; // 任意数量的元素
2. 声明宏(macro_rules!)
声明宏使用 macro_rules! 定义,是最常见的宏类型。
2.1 简单的宏定义
// 定义一个简单的宏 macro_rules! say_hello { () => { println!("Hello!"); }; } fn main() { say_hello!(); // 输出: Hello! }
2.2 带参数的宏
macro_rules! print_twice { ($x:expr) => { println!("{}", $x); println!("{}", $x); }; } fn main() { print_twice!("Rust"); print_twice!(42); }
2.3 多个匹配模式
macro_rules! greet { // 无参数 () => { println!("Hello!"); }; // 一个参数 ($name:expr) => { println!("Hello, {}!", $name); }; // 两个参数 ($greeting:expr, $name:expr) => { println!("{}, {}!", $greeting, $name); }; } fn main() { greet!(); greet!("World"); greet!("Good morning", "Alice"); }
2.4 重复模式
使用 $(...)* 或 $(…)+ 匹配重复的模式。
macro_rules! vector { ($($x:expr),*) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } fn main() { let v = vector![1, 2, 3, 4, 5]; println!("{:?}", v); let empty: Vec<i32> = vector![]; println!("{:?}", empty); }
2.5 类似vec!的宏
macro_rules! my_vec { // vec![elem; len] ($elem:expr; $n:expr) => { std::vec::from_elem($elem, $n) }; // vec![a, b, c] ($($x:expr),+ $(,)?) => { <[_]>::into_vec(Box::new([$($x),+])) }; } fn main() { let v1 = my_vec![0; 5]; println!("{:?}", v1); // [0, 0, 0, 0, 0] let v2 = my_vec![1, 2, 3]; println!("{:?}", v2); // [1, 2, 3] }
3. 捕获类型
3.1 常见的捕获类型
| 捕获类型 | 描述 | 示例 |
| expr | 表达式 | 1 + 2, “hello” |
| stmt | 语句(不含分号) | let x = 5 |
| block | 代码块 | { let x = 1; x + 2 } |
| pat | 模式 | Some(x), (a, b) |
| ty | 类型 | i32, Vec<String> |
| ident | 标识符 | foo, Bar |
| path | 路径 | std::mem::drop |
| tt | token树 | 任意token序列 |
| item | 项 | fn, struct, use等 |
3.2 使用不同捕获类型
macro_rules! create_function { // ident捕获函数名 ($func_name:ident) => { fn $func_name() { println!("你调用了 {:?}", stringify!($func_name)); } }; } create_function!(foo); create_function!(bar); fn main() { foo(); bar(); }
3.3 类型捕获
macro_rules! impl_display { ($t:ty) => { impl std::fmt::Display for $t { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } }; } struct Wrapper(i32); impl_display!(Wrapper); fn main() { let w = Wrapper(42); println!("{}", w); // 42 }
4. 高级宏技巧
4.1 重复分隔符
macro_rules! sum { // 最后一个逗号是可选的 ($($x:expr),+ $(,)?) => { 0 $(+ $x)* }; } fn main() { println!("{}", sum!(1, 2, 3)); println!("{}", sum!(1, 2, 3,)); // 末尾逗号也可以 }
4.2 重复索引
macro_rules! tuple_to_array { ($($x:expr),+ $(,)?) => { [$(($x, stringify!($x))),+] }; } fn main() { let arr = tuple_to_array!(1, 2, 3); for (val, name) in arr { println!("{} 的名字是 {}", val, name); } }
4.3 嵌套重复
macro_rules! matrix { ($($($x:expr),+);+ $(;)?) => { vec![$(vec![$($x),+]),+] }; } fn main() { let m = matrix!( 1, 2, 3; 4, 5, 6; 7, 8, 9 ); println!("{:?}", m); }
4.4 递归宏
macro_rules! calculate { // 基本情况 (eval $e:expr) => {{ let val: i32 = $e; println!("{} = {}", stringify!($e), val); val }}; // 递归展开 (eval $e:expr $(, $rest:expr)+) => {{ calculate!(eval $e); calculate!(eval $($rest),+) }}; } fn main() { calculate!(eval 1 + 2, 3 * 4, 10 / 2); }
5. 卫生宏(Hygienic Macros)
Rust的宏是卫生的,意味着宏内部定义的变量不会与外部变量冲突。
macro_rules! define_x { () => { let x = 42; println!("宏内部: x = {}", x); }; } fn main() { let x = 10; define_x!(); println!("外部: x = {}", x); // 仍然是10,没有被宏改变 }
如果需要访问外部变量,必须作为参数传入:
macro_rules! set_value { ($var:ident, $val:expr) => { $var = $val; }; } fn main() { let mut x = 10; set_value!(x, 42); println!("x = {}", x); // 42 }
6. 过程宏(Procedural Macros)
过程宏更强大也更复杂,它们直接操作Rust的抽象语法树(AST)。
6.1 过程宏类型
- 自定义派生(Derive macro):为struct和enum添加derive属性
- 属性宏(Attribute macro):定义新的属性
- 函数式宏(Function-like macro):看起来像函数调用的宏
6.2 创建过程宏项目
过程宏必须定义在单独的crate中:
cargo new my_derive --lib
编辑 Cargo.toml:
[package]
name = "my_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }
6.3 自定义Derive宏
// my_derive/src/lib.rs use proc_macro::TokenStream; use quote::quote; use syn; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { // 解析输入为DeriveInput let ast = syn::parse(input).unwrap(); // 构建trait实现 impl_hello_macro(&ast) } fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let gen = quote! { impl HelloMacro for #name { fn hello_macro() { println!("Hello, Macro! 我是 {}", stringify!(#name)); } } }; gen.into() }
使用:
// 主项目 use my_derive::HelloMacro; trait HelloMacro { fn hello_macro(); } #[derive(HelloMacro)] struct Pancakes; fn main() { Pancakes::hello_macro(); // Hello, Macro! 我是 Pancakes }
6.4 带属性的Derive宏
// my_derive/src/lib.rs use proc_macro::TokenStream; use quote::quote; use syn::{self, parse::Parser}; #[proc_macro_derive(Builder, attributes(builder))] pub fn builder_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_builder(&ast) } fn impl_builder(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let builder_name = quote::format_ident!("{}Builder", name); let gen = quote! { pub struct #builder_name; impl #name { pub fn builder() -> #builder_name { #builder_name } } }; gen.into() }
6.5 属性宏
属性宏用于定义自定义属性,可以应用于函数、结构体等。
// my_derive/src/lib.rs use proc_macro::TokenStream; use quote::quote; #[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { // attr 是属性参数,如 route("GET", "/") // item 是被装饰的项 let method = attr.to_string(); let input = syn::parse_macro_input!(item as syn::ItemFn); let name = &input.sig.ident; let body = &input.block; let expanded = quote! { #[allow(dead_code)] fn #name() { println!("路由方法: {}", #method); #body } }; expanded.into() }
使用:
use my_derive::route; #[route("GET", "/")] fn index() { println!("首页"); } fn main() { index(); }
6.6 函数式过程宏
函数式过程宏类似于 macro_rules!,但使用Rust代码实现。
// my_derive/src/lib.rs use proc_macro::TokenStream; use quote::quote; use syn; #[proc_macro] pub fn sql(input: TokenStream) -> TokenStream { // 解析SQL语句 let sql = input.to_string(); let expanded = quote! { println!("执行SQL: {}", #sql); }; expanded.into() }
使用:
use my_derive::sql; fn main() { sql!(SELECT * FROM users WHERE id = 1); }
7. 高级过程宏技巧
7.1 解析结构体字段
use proc_macro::TokenStream; use quote::quote; use syn::{self, Data, Fields}; #[proc_macro_derive(Getters)] pub fn getters_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_getters(&ast) } fn impl_getters(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let fields = match &ast.data { Data::Struct(data) => match &data.fields { Fields::Named(fields) => &fields.named, _ => panic!("只支持命名字段"), }, _ => panic!("只支持结构体"), }; let getters = fields.iter().map(|f| { let field_name = f.ident.as_ref().unwrap(); let field_ty = &f.ty; let getter_name = quote::format_ident!("get_{}", field_name); quote! { pub fn #getter_name(&self) -> &#field_ty { &self.#field_name } } }); let gen = quote! { impl #name { #(#getters)* } }; gen.into() }
7.2 错误处理
use proc_macro::TokenStream; use quote::quote; use syn::{self, spanned::Spanned}; #[proc_macro_derive(MyTrait)] pub fn my_trait_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); match impl_my_trait(&ast) { Ok(tokens) => tokens, Err(e) => e.to_compile_error().into(), } } fn impl_my_trait(ast: &syn::DeriveInput) -> Result<TokenStream, syn::Error> { // 检查是否实现了Clone let has_clone = ast.attrs.iter().any(|attr| { attr.path().is_ident("derive") && attr.parse_args::<syn::Ident>().map(|i| i == "Clone").unwrap_or(false) }); if !has_clone { return Err(syn::Error::new( ast.span(), "MyTrait需要实现Clone" )); } let name = &ast.ident; Ok(quote! { impl MyTrait for #name {} }.into()) }
8. 编译时计算
8.1 const fn 与宏
// const fn在编译时执行 const fn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } } fn main() { // 在编译时计算 const FIB_10: u64 = fibonacci(10); println!("斐波那契数列第10项: {}", FIB_10); }
8.2 include!宏
// 包含其他文件的内容 include!("some_generated_code.rs"); // 包含文件内容为字符串 const CONFIG: &str = include_str!("config.toml"); // 包含文件内容为字节数组 const BINARY: &[u8] = include_bytes!("data.bin");
9. 条件编译
9.1 cfg属性
// 只在测试时编译 #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } } // 只在特定操作系统编译 #[cfg(target_os = "linux")] fn linux_only() { println!("Linux特定代码"); } // 多条件 #[cfg(all(target_os = "linux", target_arch = "x86_64"))] fn linux_x86_64_only() {} // 任条件 #[cfg(any(target_os = "linux", target_os = "macos"))] fn unix_like() {} // 排除条件 #[cfg(not(target_os = "windows"))] fn not_windows() {}
9.2 cfg宏
fn main() { if cfg!(target_os = "linux") { println!("运行在Linux上"); } else if cfg!(target_os = "windows") { println!("运行在Windows上"); } else { println!("运行在其他系统上"); } }
10. 编译时断言
10.1 const_assert
// 使用static_assertions crate use static_assertions::const_assert; const_assert!(std::mem::size_of::<usize>() == 8); // 64位系统 fn main() { println!("编译时断言通过!"); }
10.2 编译时检查
// 使用const panic进行编译时检查 const fn check_size<T>() { assert!(std::mem::size_of::<T>() <= 128, "类型太大"); } struct SmallStruct([u8; 64]); // struct LargeStruct([u8; 256]); // 编译错误 const _: () = check_size::<SmallStruct>(); fn main() {}
11. 实际应用案例
11.1 实现自定义vec!
macro_rules! my_vec { // vec![value; count] ($val:expr; $count:expr) => {{ let count = $count; let mut vec = Vec::with_capacity(count); for _ in 0..count { vec.push($val); } vec }}; // vec![a, b, c] ($($val:expr),* $(,)?) => {{ let mut vec = Vec::new(); $(vec.push($val);)* vec }}; } fn main() { let v1 = my_vec![0; 5]; println!("{:?}", v1); // [0, 0, 0, 0, 0] let v2 = my_vec![1, 2, 3, 4, 5]; println!("{:?}", v2); // [1, 2, 3, 4, 5] }
11.2 实现HashMap初始化宏
macro_rules! hashmap { () => { ::std::collections::HashMap::new() }; ($($key:expr => $value:expr),* $(,)?) => {{ let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map }}; } fn main() { let map = hashmap! { "name" => "Alice", "age" => "30", }; println!("{:?}", map); }
11.3 实现类似try!的错误处理宏
macro_rules! try_or_return { ($expr:expr) => { match $expr { Ok(val) => val, Err(e) => { eprintln!("错误: {}", e); return; } } }; ($expr:expr, $ret:expr) => { match $expr { Ok(val) => val, Err(e) => { eprintln!("错误: {}", e); return $ret; } } }; } fn may_fail() -> Result<i32, &'static str> { Ok(42) } fn process() -> i32 { let val = try_or_return!(may_fail(), -1); val * 2 } fn main() { println!("结果: {}", process()); }
12. 调试宏
12.1 展开宏查看结果
使用 cargo expand 查看宏展开后的代码:
cargo install cargo-expand
cargo expand
12.2 编译错误定位
// 使用compile_error!产生编译错误 #[cfg(not(feature = "required_feature"))] compile_error!("必须启用required_feature特性"); fn main() {}
12.3 日志调试宏
macro_rules! debug_var { ($var:ident) => { eprintln!("[DEBUG] {} = {:?} (在{}:{})", stringify!($var), $var, file!(), line!() ); }; } fn main() { let x = 42; debug_var!(x); }
13. 最佳实践
- 从简单开始:先用 macro_rules!,必要时再用过程宏
- 文档化:为宏提供清晰的文档和示例
- 测试边界情况:宏的边缘行为可能难以预测
- 使用 $crate:避免路径问题
- 遵循命名约定:宏名通常使用 snake_case,过程宏使用 PascalCase
// 使用$crate确保路径正确 #[macro_export] macro_rules! my_macro { () => { $crate::internal_function() }; }
小结
本章深入探讨了Rust的宏系统:
- 声明宏(macro_rules!):模式匹配,适合简单的代码生成
- 过程宏:操作AST,适合复杂的自定义derive
- Derive宏:为类型自动实现trait
- 属性宏:添加自定义属性
- 函数式宏:强大的代码转换能力
宏是Rust元编程的核心,合理使用可以大大提高代码的复用性和可维护性。虽然学习曲线较陡,但掌握宏的使用将使你能够编写更加优雅和强大的Rust代码。
rust/第十六章宏.txt · 最后更改: 由 127.0.0.1
