4

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

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

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

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

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)*
      }
  };

}
4

1 回答 1

6

简短的回答是“不,您不能将item匹配器用于方法”。

根据参考资料,项目是板条箱或模块中的顶级事物,因此是函数、类型等。虽然一个structimpl块是一个项目,但它们里面的东西不是。即使在语法上,方法定义看起来与顶级函数相同,但这并不使其成为项目。

Rust 的宏系统的工作方式是,一旦一个片段被解析为一个item,例如 using $foo:item,它就永远是一个item; 一旦宏展开,它就会被拆分回标记以进行重新分析。

这样做的结果是$foo:item只能在宏的输出中的item位置,一般意思是top-level。

有几种选择。

最简单的是使用旧的tt(令牌树)匹配器。令牌树要么是非括号令牌,要么是由平衡括号包围的令牌序列;所以$(foo:tt)*匹配任何东西。但是,这意味着它也会吞噬逗号,因此在每个项目周围添加大括号会更容易:

macro_rules! create_impl {

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

      impl $struct_name {
          $($($function)*)*
      }
  };

}

然后你必须将它与额外的大括号一起使用:

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)*
      }
    }
}

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

于 2017-01-14T23:14:25.220 回答