rust macros笔记一声明式

rust Apr 19, 2021

宏是什么

宏是一种通过代码来写代码的方式。他们可以通过扩展rust的语法部分,让写重复的代码变得更容易。甚至可以扩展出rust子语言。这种写代码的方式,一般被叫做元编程。[2]

种类

rust中宏有声明宏和过程宏。[1]
声明宏:通过macro_rules!定义,必须定义在所属的cate。
过程宏:过程宏有三种,比声明宏的功能更强大。但是更难编写,也会减慢编译的速度。

  1. 自定义宏,通过#[derive]添加到struct和enum上。
  2. 类似属性的宏,可以添加到任何项目上。
  3. 类似函数的宏,可以操作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");
}


ref

  1. 官方文档
  2. wiki
  3. rust macro小书
  4. patten
  5. type