Rust中的声明宏
前言
声明式宏(Declarative macros)使得你能够写出类似 match 表达式的东西,来操作你所提供的 Rust 代码。它使用你提供的代码来生成用于替换宏调用的代码。
语法
宏通过使用macro_rules!
来声明,最为常见的一个声明式宏就是println!
。
macro_rules! 名称 {
// 规则
(元变量:类型) => {转录器,用来扩展代码};
}
- 规则(rule):至少有一个,($matcher) => {$expansion}为一个规则。
- 匹配器(matcher):可以是()、{}或[],用来匹配输入的参数。以上的
(元变量:类型)
即为一个匹配器。 - 元变量(metavariables):在匹配其中匹配元变量,可在转录器中使用。
- 转录器(transcriber): 用来在宏匹配成功后, 进行代码替换。
在一个宏中,可以有多个分支,宏根据不同的参数展开到不同的代码。每个分支可以接收多个参数,这些参数使用$
符号开头,然后跟着一个 token 类型:
-
item
:一个项(item),如一个函数,结构体,模块等。 -
block
:一个块 (block),即一个语句块或一个表达式,由花括号所包围。 -
stmt
: 一个语句(statement)。 -
pat
:一个模式(pattern),如match表达式的(pattern)。 -
expr
: 一个表达式(expression)。 -
ty
:一个类型(type),如i32,u32,String,Option等。 -
ident
: 一个标识符(indentfier)。 -
path
: 一个路径(path),如foo
,::std::mem::replace
,transmute::<_, int>
,...。 -
meta
: 一个元数据项;例如#[...]
和#![...]
属性,meta为[]内的值。 -
tt
:一个词法树(token tree)。 -
vis
:一个可能为空的Visibility
限定词,如pub
。 -
lifetime
:一个生命周期(例如'foo
,'static
, ...) -
literal
:一个字面量(e.g. "Hello World!", 3.14, ...)
使用声明式宏
创建
创建一个名叫add的过程宏,有三个分支:
- 第一个分支没有参数,在转录器中将代码替换为0。
- 第二个分支接收一个参数,并在转录器中将代码替换为它本身。
- 第三个分支接收两个参数,并在转录器中将代码替换为两个数相加。
macro_rules! add {
// 匹配器1
() => {{
0
}};
// 匹配器2
($a:expr) => {{
$a
}};
// 匹配器3
($a:expr,$b:expr) => {{
$a + $b
}};
}
fn main() {
println!("{:?}", add!()); // 0
println!("{:?}", add!(1)); // 1
println!("{:?}", add!(1, 2)); // 3
}
重复匹配
上面的例子每个匹配器只能匹配一种参数情况,如果我们要1+...+n,我们不能写n个匹配器去匹配。这时候就要用到重复匹配。可以接收可变数量的参数,类似于正则表达式:
*
:用于匹配大于等于0个参数。+
:用于匹配大于1参数。?
:用于匹配0或1个参数。
我们可以简化上面的代码为:
macro_rules! add {
// ($a:expr):要匹配的模式
// ,:分隔符,重复符号位?时不需要
// *:重复符号
($($a:expr),*)=>{{
// 没参数时返回,必须有,否则报错
0
// 重复代码块
$(+$a)*
}};
}
fn main() {
println!("{:?}", add!()); // 0
println!("{:?}", add!(1)); // 1
println!("{:?}", add!(1, 2)); // 3
}
使用声明式宏进行高级解析
我们以Student结构体为例,创建一个StudentCopy的结构体并将Student中的属性设置为pub。结构体如下:
#[derive(Debug,Clone)]
struct Student{
name:String,
age:i32,
gender:u8,
}
解析结构体
macro_rules! make_public{
(
// struct上定义的元数据
$(#[$meta:meta])*
// struct:可见范围 struct 名称
$vis:vis struct $struct_name:ident {
$(
// 字段上定义的元数据
$(#[$field_meta:meta])*
// 字段:可见范围 名称 :类型
$field_vis:vis $field_name:ident : $field_type:ty
),*$(,)+
}
) => {
$(#[$meta])*
pub struct $struct_name{
$(
$(#[$field_meta:meta])*
pub $field_name : $field_type,
)*
}
}
}
fn main() {
make_public! {
#[derive(Debug,Clone)]
struct Student{
name:String,
age:i32,
gender:u8,
}
};
let x = Student {
name: String::from("张三"),
age: 18,
gender: 1,
};
println!("{:?}", x);
}
查看输出
Student { name: "张三", age: 18, gender: 1 }
查看宏展开后代码
cargo expand
查看展开后的代码,可以看看到结构体Student相关属性前都加了pub修饰符。
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
pub struct Student {
pub name: String,
pub age: i32,
pub gender: u8,
}
#[automatically_derived]
impl ::core::fmt::Debug for Student {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(
f,
"Student",
"name",
&&self.name,
"age",
&&self.age,
"gender",
&&self.gender,
)
}
}
#[automatically_derived]
impl ::core::clone::Clone for Student {
#[inline]
fn clone(&self) -> Student {
Student {
name: ::core::clone::Clone::clone(&self.name),
age: ::core::clone::Clone::clone(&self.age),
gender: ::core::clone::Clone::clone(&self.gender),
}
}
}
let x = Student {
name: String::from("张三"),
age: 18,
gender: 1,
};
{
::std::io::_print(
::core::fmt::Arguments::new_v1(
&["", "\n"],
&[::core::fmt::ArgumentV1::new_debug(&x)],
),
);
};
}
总结
本文列出声明宏的基本使用,希望对大家有帮助。
参考
Macros in Rust: A tutorial with examples: https://blog.logrocket.com/macros-in-rust-a-tutorial-with-examples/
A Beginner’s Guide to Rust Macros: https://medium.com/@phoomparin/a-beginners-guide-to-rust-macros-5c75594498f1
留个悬念
你觉得下面这段代码会输出几?
macro_rules! create_var {
() => {
let a = 1;
};
}
fn main() {
let a = 2;
create_var!();
println!("{}", a);
}