首页 文章

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

提问于
浏览
2

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

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

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

我最初尝试使用 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 回答

  • 1

    简短的回答是“不,你不能使用 item matcher作为方法” .

    根据reference,项目是包或模块中的顶级事物,因此功能,类型等等 . 虽然 structimpl 块是一个项目,但它们内部的东西不是't. Even though syntactically, a method definition looks identical to a top level function, that doesn' t使它成为一个项目 .

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

    结果是 $foo:item 只能在项目位置的宏输出中,这通常意味着顶级 .

    有几种选择 .

    最简单的是使用好的旧 tt (标记树)匹配器 . 标记树是非括号标记或由 balancer 括号包围的标记序列;所以 $(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)*
          }
        }
    }
    

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

相关问题