【问题标题】:Preserving struct field visibility with a macro使用宏保持结构字段可见性
【发布时间】:2023-10-02 15:41:01
【问题描述】:

我正在尝试编写一个 Rust 宏,它允许我使用结构声明的字段名称和类型,但我仍然需要发出结构。

我已经让它与可选属性、结构的可见性一起使用(感谢The Little Book of Rust Macros),但无法弄清楚如何处理各个字段中pub 的可选存在。

到目前为止,我得到了:

macro_rules! with_generic {
    ($(#[$struct_meta:meta])*
    pub struct $name:ident { $($fname:ident : $ftype:ty), *}
    ) => {
        with_generic![(pub) $(#[$struct_meta])* struct $name {$($fname: $ftype) ,*}];
    };

    ($(#[$struct_meta:meta])*
    struct $name:ident { $($fname:ident : $ftype:ty), *}
    ) => {
        with_generic![() $(#[$struct_meta])* struct $name {$($fname: $ftype), *}];
    };

    (
    ($($vis:tt)*)
    $(#[$struct_meta:meta])*
    struct $name:ident { $($fname:ident : $ftype:ty), *}
    ) => {
        // emit the struct here
        $(#[$struct_meta])*
        $($vis)* struct $name {
            $($fname: $ftype,)*
        }

        // I work with fname and ftypes here
    }
}

它适用于类似的东西

with_generic! {
    #[derive(PartialEq, Eq, Debug)]
    pub struct Person {
        first_name: String,
        last_name:  String
    }
}

with_generic! {
    #[derive(PartialEq, Eq, Debug)]
    struct PrivatePerson {
        first_name: String,
        last_name:  String
    }
}

但不适用于

with_generic! {
    #[derive(PartialEq, Eq, Debug)]
    struct MixedPerson {
        pub first_name: String,
        last_name:  String
    }
}

我想获得一些关于如何使宏在最后一种情况下工作的帮助。我觉得我可能在这里遗漏了一些基本的东西,例如用于绑定可见性的类型。如果有办法在获取字段名称和类型的同时绑定整个结构树,那也很好。

我还想了解如何让它与具有生命周期参数的结构一起工作,但也许这应该是一个单独的问题。

【问题讨论】:

    标签: macros rust metaprogramming encapsulation


    【解决方案1】:

    你不能。至少,不是单一的、非递归的规则。这是因为 Rust 没有用于可见性的宏匹配器。

    parse-macros crate 包含一个 parse_struct! 宏,它显示了完全解析 struct 定义所需的工作。短版:您需要单独解析每个字段,“有pub”和“没有pub”各有一条规则。

    我还要注意另一个您的宏还没有考虑到的情况:字段上的属性,这是它们上的 doc-cmets 工作所必需的。

    很快,宏 1.1 应该会稳定下来,这可能会提供一种更简单的方法(假设您可以将宏表示为派生,并且不关心旧版本的 Rust)。

    【讨论】:

    • “我还要注意,您的宏还没有考虑另一种情况:字段上的属性,这是它们上的 doc-cmets 工作所必需的” 呃,谢谢,我知道我忘记了什么。看着你的代码(顺便说一句......令人惊叹的东西),我现在意识到我忘记了很多其他东西,比如 where 子句。不过你是对的,我本质上想要一个自定义派生:在某个地方我可以阅读更多关于宏 1.1 如何帮助的信息吗?
    • @lloydmeta 在这一点上,我不知道。我会在源代码上戳serde-derive 之类的东西。
    • 谢谢@Shepmaster。我还发现了这个cbreeden.github.io/Macros11
    【解决方案2】:

    在我提出这个问题后不久,Rust 1.15 是 officially released,并带来了 @DK 之类的 procedural macros(自定义派生)支持。说。

    展望未来,我认为自定义派生 w/ syn 和 quote 将是做这种事情的标准方式,并完全回避这个问题,因为您不再需要手动重新发出结构。

    【讨论】:

      【解决方案3】:

      从 Rust 1.30 开始,您可以将可见性关键字与 vis 说明符匹配。如果没有要匹配的可见性关键字,vis 元变量将不匹配任何内容,因此您甚至不需要在 $()* 中使用它。这一变化使with_generic 变得更加简单:

      macro_rules! with_generic {
          ($(#[$struct_meta:meta])*
          $sv:vis struct $name:ident { $($fv:vis $fname:ident : $ftype:ty), *}
          ) => {
              // emit the struct here
              $(#[$struct_meta])*
              $sv struct $name {
                  $($fv $fname: $ftype,)*
              }
              // do whatever else you need here
          }
      }
      

      【讨论】:

        最近更新 更多