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::replacetransmute::<_, 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);
}

posted @ 2022-12-10 17:15  gaozejie  阅读(314)  评论(0编辑  收藏  举报