「深挖Rust」Rust macro 实例 - 3

「这是我参与2022首次更文挑战的第 19 天,活动详情查看:2022首次更文挑战」。


macro_rules! ok_or_return{
    // internal rule.
    (@error $a:ident,$($b:tt)* )=>{
        {
        match $a($($b)*) {
            Ok(value)=>value,
            Err(err)=>{
                return Err(err);
            }
        }
        }
    };

    // public rule can be called by the user.
    ($a:ident($($b:tt)*))=>{
        ok_or_return!(@error $a,$($b)*)
    };
}

fn some_work(i: i64, j: i64) -> Result<(i64, i64), String> {
    if i + j > 2 {
        Ok((i, j))
    } else {
        Err("error".to_owned())
    }
}

fn main() -> Result<(), String> {
    // instead of round bracket curly brackets can also be used
    ok_or_return! {some_work(1,4)};
    ok_or_return!(some_work(1, 0));
    Ok(())
}
复制代码

高级解析

宏有时会执行一些需要解析Rust语言本身语法的任务。

现在把我们到目前为止所涉及的所有概念放在一起。下面我们创建一个宏,通过 pub 关键字使一个结构变得公开。

首先,我们需要解析 Rust struct,获得 structname, fields, field type

解析 name 和 field

struct 声明在开始时有一个可见性关键字(如pub),接着是 struct 自身关键字,然后是 struct 的名称和 struct 的内容:

image.png

macro_rules! make_public {
    (   
        // use vis type for visibility keyword and ident for struct name
        $vis:vis struct $struct_name:ident { }
    ) => {
        {
            pub struct $struct_name{ }
        }
    }
}
复制代码
  1. $vis → 可见性
  2. $struct_name → 结构名称

要使一个 struct 公开,我们只需要返回添加pub关键字的结果,并忽略 $vis 变量之前的值。

image.png 一个 struct 可以包含不同的数据类型和可见性的多个字段。

  1. ty标记类型用于数据类型
  2. vis用于可见性
  3. ident用于字段名
  4. 零个或多个字段使用 * 重复的方式
macro_rules! make_public {
    (
        $vis:vis struct $struct_name:ident {
            $(
                // vis for field visibility, ident for field name and ty for field data type
                $field_vis:vis $field_name:ident : $field_type:ty
            ),*
        }
    ) => {
        {
            pub struct $struct_name{
                $(pub $field_name : $field_type,)*
            }
        }
    }
}
复制代码

解析元数据

通常情况下,struct 会被附加一些元数据或派生宏(过程宏的一种),例如 #[derive(Debug)]。这些元数据需要保持完整。而解析这些元数据是使用 meta 类型完成的。

macro_rules! make_public{
    (
     // meta data about struct
     $(#[$meta:meta])* 
     $vis:vis struct $struct_name:ident {
        $(
        // meta data about field
        $(#[$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,
                )*
            }
        }
    }
}
复制代码

make_public! 现在已经准备好了。为了看看 make_public! 是如何工作的,让我们在 Rust Playground 中将宏使用到实际编译的代码中:

fn main(){
    make_public!{
        #[derive(Debug)]
        struct Name{
            n:i64,
            t:i64,
            g:i64,
        }
    }
}
复制代码

而宏扩展代码如下:

macro_rules! make_public {
    ($ (#[$ meta : meta]) * $ 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() {
    pub struct name {
        pub n: i64,
        pub t: i64,
        pub g: i64,
    }
}
复制代码

声明宏的局限性

声明式宏有一些限制。有些与Rust宏本身有关,有些则是针对声明式宏的:

  • 缺少对宏的自动完成和扩展的支持
  • 调试声明式宏很困难
  • 修改能力有限
  • 会生成较大的二进制文件
  • 较长的编译时间(这同时适用于声明性和过程宏)

猜你喜欢

转载自juejin.im/post/7066423631793881101