如何在另一个宏中定义一个具有无限量参数的宏?

时间:2017-08-16 16:50:16

标签: rust

这样可行,但test_macro只接受一个参数:

macro_rules! test_define (
    ($name:ident) => (
        macro_rules! $name (
            ( $x:expr ) => (
                // something
            )
        );
    )
);

test_define!(test_macro);

如果我尝试这样做:

macro_rules! test_define2 (
    ($name:ident) => (
        macro_rules! $name (
            ( $($x:expr),* ) => (
                // something
            )
        );
    )
);

test_define2!(test_macro2);

编译失败:

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
 --> src/main.rs:4:16
  |
4 |             ( $($x:expr),* ) => (
  |                ^^^^^^^^^

2 个答案:

答案 0 :(得分:5)

nested macros don't allow repetitions in binding patterns (issue #35853)已知错误。

不幸的是,没有解决方法。唯一的解决方案是将API更改为不依赖于嵌套宏中的重复。

答案 1 :(得分:3)

虽然无法直接执行此操作({3}},但您可以使用kennytm described in this answer执行此操作(如果您确实需要,否则我不会推荐 >)。 虽然使用procedural macros时可能要简单得多,但stable也可以这样做。

限制

如果我错过任何事情或者将来使用nightly这样的话可能会出现问题,请告诉我。

  • 您需要创建一个结构,这会导致以下问题之一
    1. 每个模块最多有1个宏调用
    2. 在调用外部宏
    3. 时,用户需要添加另一个字符串
    4. 您创建一个包含所需结构且可以访问的私有模块

所以,让我们只复制custom derive中的示例,然后查看代码的这一部分:

fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
    let name = &ast.ident;
    quote! {
        impl HelloWorld for #name {
            fn hello_world() {
                println!("Hello, World! My name is {}", stringify!(#name));
            }
        }
    }
}

quote!宏内部,您不仅限于struct的实施。你可以改成这个

quote! {
    macro_rules! #name {
         ($($expr:expr),*) => {
         // something
         }
    }
}

现在你有一个与struct具有相同名称的宏,它带有无限量的参数。

要在另一个宏中执行此操作,外部宏只需看起来像这样:

macro_rules! test_define {
    ($name:ident) => {
        #[allow(non_camel_case_types)] // because macro names are lower case
        #[allow(dead_code)] // because this struct should never be used
        #[derive(HelloWorld)]
        struct $name { }
    }
};

现在你可以调用test_define然后调用内部宏:

test_define!(foo);

fn main() {
    foo!()
}

但是,仍有一个问题:人们可能会意外访问您的结构。所以有办法规避这个问题(每个解决方案都是将问题与相同数字直接联系起来):

  1. 以一种防止意外访问的方式命名结构:

    macro_rules! test_define {
        ($name:ident) => {
            #[allow(dead_code)]
            #[derive(HelloWorld)]
            struct Dgfggsdfgujksdsdsdfsdfsdg { 
                $name: u8,
            }
        }
    };
    

    您必须在struct field内更改here以使用name代替quote!,以防test_define!被调用多次test_define!你有2个具有相同名称的结构,导致编译时错误。

  2. 为防止两个相同的结构名称,您还可以更改macro_rules! test_define { ($name:ident, $rep_guard:ident) => { #[allow(non_camel_case_types)] #[allow(dead_code] #[derive(HelloWorld)] struct $rep_guard { $name: u8, } } }; 以获取其他参数:

    struct field

    您使用name代替test_define!(foo,fvgfdgdfgdfgd)此方法。现在使用你必须写struct,这真的很尴尬,所以我不推荐这个。

  3. 这可能是最好的选择,现在您可以保留解决方案1 ​​中的奇怪module名称,并将整个内容放在test_define!中。这意味着没有人可以意外地访问创建的结构,并且您可以对macro_rules! test_define { ($name:ident) => { #[macro_use] // to use the macro in the current module mod $name { #[allow(dead_code)] #[derive(HelloWorld)] struct Dgfggsdfgujksdsdsdfsdfsdg { $name: u8, } } } }; 进行无限量的调用。

    dead_code
  4. 编译器应该只删除所有这些结构,因为它们是--release (至少在使用quote!标志构建时)。如果需要,您可以通过添加#[macro_export]来调整String

    另一个优点是proc宏使用您的源代码的方式与TokensString相同,可以转换为test_derive!(foo),这意味着您可以创建多个宏,例如:

    foo!() => foo_with_var!(75)HapiContext context = new DefaultHapiContext(modelClassFactory) PipeParser pipeParser = context.getPipeParser(); pipeParser.setValidationContext(new NoValidation()); pipeParser.getParserConfiguration().setAllowUnknownVersions(true); Message msg = pipeParser.parse(document); Parser xmlParser = context.getXMLParser(); return xmlParser.encode(msg);

    如果你有什么不明白的地方,请问。

相关问题