rust macros笔记一声明式
宏是什么
宏是一种通过代码来写代码的方式。他们可以通过扩展rust的语法部分,让写重复的代码变得更容易。甚至可以扩展出rust子语言。这种写代码的方式,一般被叫做元编程。[2]
种类
rust中宏有声明宏和过程宏。[1]
声明宏:通过macro_rules!定义,必须定义在所属的cate。
过程宏:过程宏有三种,比声明宏的功能更强大。但是更难编写,也会减慢编译的速度。
- 自定义宏,通过#[derive]添加到struct和enum上。
- 类似属性的宏,可以添加到任何项目上。
- 类似函数的宏,可以操作token作为他们的参数。
宏和函数的对比
宏在某些方面和函数是很类似的,在rust中调用宏和函数几乎相同。例如:println!("hello world");
不过宏有一些函数没有的能力,函数必须声明参数个数和类型,宏可以接受不同数量的参数。
宏可以在一个给定的类型上实现trait,函数不行[me]。最后一个重要的区别:在一个文件里调用宏之前必须定义它,或将其引入作用域,而函数则可以在任何地方定义和调用。
宏相对于函数的定义要更复杂。所以更难于维护和阅读理解。
[me]:我理解宏是在编译器展开的,函数里面是不能做这件事情的。因为rust里面trait是要在编译期间就实现,然后编译成机器码。
声明宏
声明宏使用macro_rules!定义,类似于match语法,决定某个分支的调用。
简单的例子
macro_rules! hello_world {
() => { println!("Hello World!"); };
}
fn main() {
hello_world!();
}
上面的例子会打印hello world到控制台。宏的调用是通过定义加!的方式。宏在使用前必须声明,函数可以在其后声明。
语法
[me]:宏的语法是很复杂的,我把常用的功能记录下来,其余可以参考官方文档[3]
定义
macro_rules! $name {
$rule0 ;
$rule1 ;
// …
$ruleN ;
}
主要是由三部分组成:macro_rules!关键字, $name(宏的定义名), $rule(对应的匹配规则)。类似rust的match语法。
最重要的rule的写法,下面我们详细讲解。
rule的定义
rule的定义如下:($pattern) => {$expansion}
rule的匹配和捕获
macro本质上是对token流的处理。主要有匹配和捕获两种方式。匹配的话,目前我了解是精确匹配。模糊匹配的功能可以通过捕获来实现。
rule的捕获
定义:
macro_rules! $name {
($e:expr) => {...};
}
expr必须是如下之一:
- item: an item, like a function, struct, module, etc.
- block: a block (i.e. a block of statements and/or an expression, surrounded by braces)
- stmt: a statement
- pat: a pattern [4]
- expr: an expression
- ty: a type [5]
- ident: an identifier
- path: a path (e.g. foo, ::std::mem::replace, transmute::<_, int>, …)
- meta: a meta item; the things that go inside #[...] and #![...] attributes
- tt: a single token tree
常用的应该就是expr(表达式)和tt(语法树)了。rust表达式经常使用的一个概念,复杂的场景一般用tt。
eg: 一个将表达式结果乘以5的macro
macro_rules! times_five {
($e:expr) => {5 * $e};
}
rule捕获的重复
捕获的规则是可以重复,意思是我们定义一个规则,可以重复去捕获符合规则的token。重复的rule定义如下:
$ ( ... ) sep rep.
完整定义
macro_rules! $name {
($($e: expr) sep rep ) => { $expresions },
...
}
sep是定义的分隔符,一般是,或者;
rep是重复次数,可以为 * 表示零或无数次,+ 表示一次或者无数次。
[me]:研究发现表达式和sep、rep之间不用加空格,并且后面还可以写多个匹配规则。
一个类似vec!的例子:
macro_rules! vec_strs {
(
// Start a repetition:
$(
// Each repeat must contain an expression...
$element:expr
)
// ...separated by commas...
,
// ...zero or more times.
*
) => {
// Enclose the expansion in a block so that we can use
// multiple statements.
{
let mut v = Vec::new();
// Start a repetition:
$(
// Each repeat will contain the following statement, with
// $element replaced with the corresponding expression.
v.push(format!("{}", $element));
)*
v
}
};
}
fn main() {
let test = vec_strs![1,2,3];
assert_eq!(test[0], "1");
}