是否可以使用传递给宏的项arg作为方法?

时间:2017-01-14 17:51:53

标签: rust rust-macros

我尝试创建一个生成struct的宏,该宏提供了一组传递给宏的方法。例如,调用:

create_impl!(StructName, fn foo() -> u32 { return 432 })

应该生成一个提供方法StructName的空结构foo()

我最初尝试使用item宏arg类型。但是,当我尝试在规则中使用item时,我收到以下编译器错误:

error: expected one of `const`, `default`, `extern`, `fn`, `pub`, `type`, `unsafe`, or `}`, found `fn foo() -> u32 { return 42; }`
  --> src/lib.rs:40:13
   |
40 |           $($function)*
   |             ^^^^^^^^^

是否可以使用item参数以这种方式定义生成的结构中的方法?我有什么遗失的吗?

这是我定义的完整宏:

macro_rules! create_impl {

  ($struct_name:ident, $($function:item),*) => {
      struct $struct_name {
      }

      impl $struct_name {
          // This is the part that fails.
          $($function)*
      }
  };

}

1 个答案:

答案 0 :(得分:1)

简短的回答是"不,你不能使用struct匹配器来获取方法"。

根据reference,项目是包或模块中的顶级事物,因此功能,类型等等。虽然implitem块是一个项目,但它们内部的内容并非如此。即使在语法上,方法定义看起来与顶级函数相同,但它并不能使它成为一个项目。

Rust的宏系统的工作方式是,一旦片段被解析为$foo:item,例如使用item,它永远是$foo:item;一旦宏被扩展,它就会被拆分成令牌进行重新分析。

结果是tt只能在项目位置的宏输出中,这通常意味着顶级。

有几种选择。

最简单的方法是使用好的旧$(foo:tt)*(标记树)匹配器。标记树是非括号标记或由平衡括号包围的标记序列;所以 ($struct_name:ident, $({ $($function:tt)* }),*) => { struct $struct_name { } impl $struct_name { $($($function)*)* } }; } 匹配任何东西。但是,这意味着它也会吞噬逗号,所以在每个项目周围添加大括号更容易:

macro_rules! create_impl {

create_impl!(StructName, { fn foo() -> u32 { return 432 } }, { fn bar() -> u32 { return 765 } });

然后你必须使用额外的括号:

item

您也可以直接匹配所需的语法,而不是委托给macro_rules! create_impl2 { ($struct_name:ident, $(fn $fname:ident($($arg:tt)*) -> $t:ty $body:block),*) => { struct $struct_name { } impl $struct_name { $(fn $fname($($arg)*) -> $t $body)* } } } 匹配器:

options(digits=20)

当然,因为它是显式的,这意味着如果你想支持没有返回类型的函数,你需要在你的宏中添加另一个案例。