宏的定义

  1. 声明宏:也称为macro_rules!宏,使用macro_rules!关键字定义。它是一种基于模式匹配的文本替换宏,类似于C语言中的宏定义。声明宏在编译期展开,用匹配的代码片段替换宏调用处的代码。
  2. 过程宏:是一种更为高级的宏,它通过编写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); // 输出:Result: 30
}

在匹配器中,$(名称):匹配段选择器 这种句法格式匹配符合指定句法类型的 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);
}
}
}
// 输出
// Sum: 499999500000
// Execution time: 4.322637ms

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), // 使用 {:?} 打印 Debug 格式
}
}
}
};
}
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);
},
}
}
//pat 就是用来匹配if let,match,while let的模式的
// ($queue:expr, $($pat:pat => $body:expr),* $(,)?)
// ,*表示用,分割
//$(,)?匹配最后一个可能存在的逗号

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()); // 输出: Value: 0
println!("Value: {}", get_value_String()); // 输出: Value: ""
}

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(); // 输出:This is the function named 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; }); // 匹配方括号包围的组
}
//Matched group: "(a + b)"
//Matched group: "[1, 2, 3]"
//Matched group: "{ let x = 42; }"
//stringify!代码段转字面量

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); // 匹配生命周期标注 'a
print_lifetime!('static); // 匹配生命周期标注 '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); // 匹配 pub
print_visibility!(pub(crate)); // 匹配 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 去匹配列表中的所有标识符。

在转码过程中,重复元会受到额外的限制,以便于编译器知道该如何正确地扩展它们:

  1. 在转码器中,元变量必须与它在匹配器中出现的次数、指示符类型以及其在重复元内的嵌套顺序都完全相同。因此,对于匹配器 $( $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);//将输入的 TokenStream 解析为 DeriveInput 类型,以便进一步处理。

let name = input.ident;//获取结构体或枚举的名称。
let gen = quote! {//quote用于生成 Rust 代码。
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct(stringify!(#name)).finish()//生成一个简单的 Debug 实现,只打印结构体的名称。
}
}
};

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.");
}
  • #[test]
1
2
3
4
#[test]
fn test_addition() {
assert_eq!(1 + 1, 2);
}
  • #[inline]
1
2
3
4
#[inline]
fn inline_function() {
println!("This function is inlined");
}

本站由 yyb 使用 Stellar 1.29.1 主题创建。 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。