宏的定义
- 声明宏:也称为macro_rules!宏,使用macro_rules!关键字定义。它是一种基于模式匹配的文本替换宏,类似于C语言中的宏定义。声明宏在编译期展开,用匹配的代码片段替换宏调用处的代码。
- 过程宏:是一种更为高级的宏,它通过编写Rust代码来处理输入的代码,并在编译期间生成新的代码。过程宏主要用于属性宏(Attribute Macros)、类函数宏(Function-Like Macros)和派生宏(Derive Macros)等场景。
声明宏
1 2 3 4 5 6 7 8 9
| macro_rules! print_message { () => { println!("Hello, World!"); }; }
fn main() { print_message!(); }
|
参数匹配
1 2 3 4 5 6 7 8 9 10
| macro_rules! add { ($x:expr, $y:expr) => { $x + $y }; }
fn main() { let result = add!(10, 20); println!("Result: {}", result); }
|
在匹配器中,$(名称):匹配段选择器 这种句法格式匹配符合指定句法类型的 Rust 句法段,并将其绑定到元变量$(名称)上。
有效的匹配段选择器包括:
- item: 程序项
- block: 块表达式
- stmt: 语句,注意此选择器不匹配句尾的分号(如果匹配器-中提供了分号,会被当做分隔符),但碰到分号是自身的一部分的程序项语句的情况又会匹配。
- pat: 模式
- expr: 表达式
- ty: 类型
- ident: 标识符或关键字
- path: 类型表达式 形式的路径
- tt: token树 (单个 token 或宏匹配定界符 ()、[] 或{} 中的标记)
- meta: 属性,属性中的内容
- lifetime: 生存期token
- vis: 可能为空的可见性限定符
- literal: 匹配 -?字面量表达式
参数匹配器
item
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| macro_rules! add_attribute { ($item:item) => { #[derive(Debug)] $item }; } add_attribute! { struct MyStruct { field1: i32, field2: String, } }
#[derive(Debug)] struct MyStruct { field1: i32, field2: String, }
|
block
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| macro_rules! measure_time { ($block:block) => { { let start = std::time::Instant::now(); $block let duration = start.elapsed(); println!("Execution time: {:?}", duration); } }; } fn main() { measure_time! { { let mut sum = 0; for i in 0..1000000 { sum += i; } println!("Sum: {}", sum); } } }
|
stmt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| macro_rules! print_statements { ($($stmt:stmt)*) => { { $($stmt)* } }; } rust
fn main() { print_statements! { let x = 10 println!("x = {}", x) let y = 20 println!("y = {}", y) } }
|
pat
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 28 29 30 31 32 33 34 35 36 37
| macro_rules! process_messages { ($queue:expr, $($pat:pat => $body:expr),* $(,)?) => { { for msg in $queue { match msg { $($pat => $body),* _ => println!("Unhandled message: {:?}", msg), } } } }; } fn main() { let messages = vec![ Message::Connect { user_id: 1, username: String::from("Alice") }, Message::Chat { user_id: 1, message: String::from("Hello, world!") }, Message::Disconnect { user_id: 1 }, Message::Unknown, ];
process_messages! { messages, Message::Connect { user_id, username } => { println!("User {} ({}) connected", user_id, username); }, Message::Chat { user_id, message } => { println!("User {} says: {}", user_id, message); }, Message::Disconnect { user_id } => { println!("User {} disconnected", user_id); }, } }
|
expr
- expr 匹配器用于匹配任意有效的 Rust 表达式,表达式是计算并返回值的代码片段,例如 1 + 2、x * y、foo()
- expr 不匹配语句(stmt),因为语句通常以分号 ; 结尾,而表达式通常不以分号结尾。
1 2 3 4 5 6 7 8 9 10 11 12 13
| macro_rules! print_expr { ($expr:expr) => { println!("The value of the expression is: {}", $expr); }; } fn main() { let x = 10; let y = 20;
print_expr!(x + y); print_expr!(x * y); print_expr!(x / y); }
|
ty
1 2 3 4 5 6 7 8 9 10 11 12 13
| macro_rules! define_function { ($name:ident, $ty:ty) => { fn $name() -> $ty { <$ty>::default() } }; } define_function!(get_value_i32, i32); define_function!(get_value_String, String); fn main() { println!("Value: {}", get_value_i32()); println!("Value: {}", get_value_String()); }
|
ident
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| macro_rules! create_function { ($prefix:ident, $suffix:ident) => { fn $prefix$suffix() { println!("This is the function named {}{}", stringify!($prefix), stringify!($suffix)); } }; }
fn main() { create_function!(my, function);
myfunction(); }
|
path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| macro_rules! call_function_with_args { ($func:path, $($arg:expr),*) => { $func($($arg),*); }; }
mod my_module { pub fn my_function(arg1: i32, arg2: &str) { println!("Function called with arguments: {} and {}", arg1, arg2); } }
fn main() { call_function_with_args!(my_module::my_function, 42, "Hello"); }
|
- path 用于匹配模块路径、类型名、函数名或其他命名项的路径。它可以包含多个部分,例如 std::collections::HashMap 或 my_module::my_function。
- expr 用于匹配任意有效的 Rust 表达式。表达式可以是字面量、变量、函数调用、运算符表达式等
- 比如上面的例子path匹配的是函数my_function,而使用expr只能匹配一整个my_function(arg1: i32, arg2: &str)的表达式
tt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| macro_rules! print_group { ($t:tt) => { println!("Matched group: {:?}", stringify!($t)); }; }
fn main() { print_group!((a + b)); print_group!([1, 2, 3]); print_group!({ let x = 42; }); }
|
ifetime
1 2 3 4 5 6 7 8 9 10
| macro_rules! print_lifetime { ($lt:lifetime) => { println!("Matched lifetime: {:?}", stringify!($lt)); }; }
fn main() { print_lifetime!('a); print_lifetime!('static); }
|
vis
1 2 3 4 5 6 7 8 9 10 11
| macro_rules! print_visibility { ($vis:vis) => { println!("Matched visibility: {:?}", stringify!($vis)); }; }
fn main() { print_visibility!(pub); print_visibility!(pub(crate)); print_visibility!(); }
|
literal
- literal 用于匹配字面量表达式,包括整数、浮点数、字符串、布尔值等。literal 也可以匹配带符号的字面量(如 -42)。
1 2 3 4 5 6 7 8 9 10 11 12 13
| macro_rules! print_literal { ($lit:literal) => { println!("Matched literal: {:?}", stringify!($lit)); }; }
fn main() { print_literal!(42); print_literal!(-42); print_literal!("hello"); print_literal!(true); print_literal!(3.14); }
|
重复元
在匹配器和转码器中,重复元被表示为:将需要重复的 token 放在 $(…) 内,然后后跟一个重复运算符(repetition operator),这两者之间可以放置一个可选的分隔符(separator token)。分隔符可以是除定界符或重复运算符之外的任何 token,其中分号(;)和逗号(,)最常见。例如: $( $i:ident ),* 表示用逗号分隔的任何数量的标识符。嵌套的重复元是合法的。
重复运算符为:
- * — 表示任意数量的重复元。
- + — 表示至少有一个重复元。
- ? — 表示一个可选的匹配段,可以出现零次或一次。
因为 ? 表示最多出现一次,所以它不能与分隔符一起使用。
通过分隔符的分隔,重复的匹配段都会被匹配和转码为指定的数量的匹配段。元变量就和这些每个段中的重复元相匹配。例如,之前示例中的 $( $i:ident ),* 将 $i 去匹配列表中的所有标识符。
在转码过程中,重复元会受到额外的限制,以便于编译器知道该如何正确地扩展它们:
- 在转码器中,元变量必须与它在匹配器中出现的次数、指示符类型以及其在重复元内的嵌套顺序都完全相同。因此,对于匹配器 $( $i:ident ),,转码器 => { $i }, => { $( $( $i) )* } 和 => { $( $i )+ } 都是非法的,但是 => { $( $i );* } 是正确的,它用分号分隔的标识符列表替换了逗号分隔的标识符列表。
2.转码器中的每个重复元必须至少包含一个元变量,以便确定扩展多少次。如果在同一个重复元中出现多个元变量,则它们必须绑定到相同数量的匹配段上,不能有的多,有的少。例如,( $( $i:ident ),* ; $( $j:ident ),* ) => (( $( ($i,$j) ),* )) 里,绑定到 $j 的匹配段的数量必须与绑定到 $i 上的相同。这意味着用 (a, b, c; d, e, f) 调用这个宏是合法的,并且可扩展到 ((a,d), (b,e), (c,f)),但是 (a, b, c; d, e) 是非法的,因为前后绑定的数量不同。此要求适用于嵌套的重复元的每一层。
过程宏
类函数宏
在Rust中,类函数宏是一种特殊的宏,它允许开发者创建类似函数调用的宏,并在编译期间对代码进行生成和转换。类函数宏使用proc_macro模块中的TokenStream类型来处理输入和输出。类函数宏的定义基本形式如下:
1 2 3 4 5 6 7 8 9
| extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro] pub fn function_macro(input: TokenStream) -> TokenStream { }
|
标准库中
- println!
- format!
- vec!
- stringify!
- concat!
- include_str! 将多个字符串字面量连接成一个字符串字面量。
- include_bytes! 将文件内容作为字符串字面量包含到代码中。
派生宏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Debug)] pub fn derive_debug(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput);
let name = input.ident; let gen = quote! { impl std::fmt::Debug for #name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct(stringify!(#name)).finish() } } };
gen.into() }
|
实际使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| use my_debug_derive::Debug;
#[derive(Debug)] struct MyStruct { field1: i32, field2: String, }
fn main() { let my_struct = MyStruct { field1: 42, field2: "hello".to_string(), };
println!("{:?}", my_struct); }
|
标准库中
- #[derive(Debug)]
- 它用于为结构体或枚举自动生成 Debug trait 的实现,使得这些类型可以使用 println!(“{:?}”, value) 进行调试打印。
- #[derive(PartialEq, Eq)]
- #[derive(PartialEq, Eq)] 用于为结构体或枚举自动生成 PartialEq 和 Eq trait 的实现,使得这些类型可以使用 == 和 != 运算符进行比较。
- #[derive(Copy, Clone)]
- #[derive(Copy, Clone)] 用于为结构体或枚举自动生成 Copy 和 Clone trait 的实现,使得这些类型可以被复制和克隆
- #[derive(Serialize, Deserialize)]
- #[derive(Serialize, Deserialize)] 是 serde crate 提供的过程宏,用于为结构体或枚举自动生成序列化和反序列化的代码。虽然这不是标准库的一部分,但它是一个非常常见的第三方库,广泛用于 Rust 项目中。
- #[derive(Hash)]
- #[derive(Hash)] 用于为结构体或枚举自动生成 Hash trait 的实现,使得这些类型可以被用作哈希集合(如 HashSet)的键。
属性宏
- #[cfg] 是一个条件编译属性,用于根据配置条件编译代码。
1 2 3 4 5 6 7 8 9
| #[cfg(target_os = "linux")] fn platform_specific_function() { println!("Running on Linux"); }
#[cfg(feature = "my_feature")] fn foo() { println!("This code is only compiled when `my_feature` is enabled."); }
|
1 2 3 4
| #[test] fn test_addition() { assert_eq!(1 + 1, 2); }
|
1 2 3 4
| #[inline] fn inline_function() { println!("This function is inlined"); }
|