使用宏保持结构字段可见性

Preserving struct field visibility with a macro

我正在尝试编写一个允许我使用字段名称和结构声明类型的 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
    }
}

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

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

你不能。至少,没有一个 non-recursive 规则。这是因为 Rust 没有用于可见性的宏匹配器。

parse-macros crate contains a parse_struct! 宏显示了完全解析 struct 定义所需的工作。简短版本:您需要单独解析每个字段,每个 "has pub" 和 "doesn't have pub".

都有一个规则

我还要注意,还有 另一个 情况,您的宏尚未考虑:字段上的属性,doc-comments 需要这些属性上班。

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

Rust 1.15 是 officially released shortly after I asked this question and brings procedural macros(自定义派生)支持,如 @DK。说。

展望未来,我认为自定义派生 w/syn 和引用将是做这种事情的标准方式,side-steps 这个问题完全解决,因为您不再需要手动 re-emit结构。

从 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
    }
}