当前位置: 首页 > 知识库问答 >
问题:

在宏示例中,模块路径类型名称应使用哪些片段说明符(元变量类型)?

陆博易
2023-03-14

我发现自己写的代码是这样的:

// Of course I could add a use statement, but there's still undesireable duplication.
// in the real code theres anywhere from 3 to 10 items in the tuple,
// but that would just clog up this example
pub fn cols() -> (crate::foo::bar::A, crate::foo::bar::B) {
    (crate::foo::bar::A, crate::foo::bar::B)
}

很多次了。我试图创建一个宏来为我输出此函数:

macro_rules! impl_cols {
    ( $namespace:path, $($col_name:ty,)*) => {
        pub fn cols() -> ( $( $namespace::$col_name, )* ) {
            ( $( $namespace::$col_name, )* )
        }
    }
}

但无论我为元变量选择什么片段说明符(pathidenttytt,它们的组合),它都会出错。有什么魔法咒语能让它起作用?

类型AB如下所示:

// In "foo.rs"

pub mod bar {
    pub struct A;
    pub struct B;
}

铁锈操场上的一个(错误)例子。

当然这里有一个XY问题:我使用的是diesel板条箱,它的结构上有Queryable派生impl,我希望能够。选择()。虽然可能还有其他解决方案,但我仍然想了解我编写的宏为什么不起作用,以及如果有任何问题,什么会起作用。


共有1个答案

蒯翰墨
2023-03-14

你可以用tt咀嚼来做到:

macro_rules! impl_cols {
    (@build-tuple
        ( $($types:path,)* )
        ( $($ns:ident)::* )
        ( $col_name:ident, $($rest:tt)* )
    ) => {
        impl_cols! { @build-tuple
            (
                $($types,)* 
                $($ns::)* $col_name,
            )
            ( $($ns)::* )
            ( $($rest)* )
        }
    };
    // Empty case
    (@build-tuple
        ( $($types:path,)* )
        ( $($ns:ident)::* )
        ( )
    ) => {
        ( $($types,)* )
    };
    (
        $($ns:ident)::*,
        $($col_name:ident,)*
    ) => {
        pub fn cols() -> impl_cols! { @build-tuple
            ( )
            ( $($ns)::* )
            ( $($col_name,)* )
        } {
            impl_cols! { @build-tuple
                ( )
                ( $($ns)::* )
                ( $($col_name,)* )
            }
        }
    };
}

操场。

编辑:

简单匹配不起作用的原因是无法在Rust宏中连接路径(没有过程宏的帮助)。这是因为,引用参考文献(https://doc.rust-lang.org/reference/macros-by-example.html#transcribing):

通过示例将匹配的片段转发给另一个宏时,第二个宏中的匹配器将看到片段类型的不透明AST。第二个宏不能使用文字标记来匹配匹配器中的片段,只能使用相同类型的片段说明符。ident、life和tt片段类型是一个例外,可以通过文本标记进行匹配。

这不仅在转发到另一个宏时是正确的,而且在转发到编译器时也是正确的。

您想要构建两个不同的AST片段:返回类型(元组中的每个类型)的Type和主体的Express。也就是说,crate::foo::bar::A(例如)履行两个角色:在返回类型中,它是一个类型(特别是TypePath),在正文中,它是一个表达式(具体地说Path表达式)。

如果我们看一下这两个的定义TypePathPath表达式),我们会发现它们基本上等于以下内容(忽略泛型和函数路径等不相关的部分):

路径:

PathIdentintSegment:

如果您不熟悉EBNF符号,这意味着标识符列表(: identinmacro_rules!),用::s分隔。

所以,当你在做这样的事情时:

macro_rules! concat_ns {
    ($ns:path, $type:ident) => {
        fn my_fn() -> $ns :: $type { todo!() }
    };
}
concat_ns!(crate::foo::bar, A)

宏调用将生成类似以下内容的AST:

MacroInvocation
  ...
    Path
      PathIdentSegment `crate`
      PathIdentSegment `foo`
      PathIdentSegment `bar`
    COMMA
    IDENTIFIER `A`

您的宏希望生成类似以下内容的AST:

Function
  ...
  FunctionReturnType
    Type
      Path
        <Insert metavariable $ns here>
        <Insert metavariable $type here>

这给了你:

Function
  ...
  FunctionReturnType
    Type
      Path
        Path
          PathIdentSegment `crate`
          PathIdentSegment `foo`
          PathIdentSegment `bar`
        PathIdentSegment `A`

但这是一个无效的AST,因为Path只能包含pathIdentitSegment而不能包含其他Paths!(注意:这不是确切的过程,但大致相同)。

现在您也了解了tt-munching解决方案的工作原理:在这里,我们从不创建路径节点,只保留原始标识符。我们可以连接原始标识符并从中创建一个路径(这通常是使用tt munchers的原因:当我们不需要使用Rust宏的语法片段捕获能力时,因为我们希望在之后恢复它们)。

 类似资料:
  • 通读ANSI C Yacc语法规范后,我注意到以下内容都是有效的: 这对我来说似乎很奇怪,因为我对类型的理解表明这些变量都没有类型。这些是什么意思?如何检查它们的类型?分配了多少内存?

  • 本文向大家介绍Rust 片段说明符—模式的种类,包括了Rust 片段说明符—模式的种类的使用技巧和注意事项,需要的朋友参考一下 示例 在中$e:expr,expr称为片段说明符。它告诉解析器该参数$e期望什么样的令牌。Rust提供了多种片段说明符,使输入非常灵活。 说明符 描述 例子 ident 识别码 x, foo path 合格名称 std::collection::HashSet, Vec:

  • 问题内容: 我在Go中找到了符文类型,并有一个简单的问题,但值得一提。 我发现它是int32的别名,目的是区分数字和字符值。 http://golang.org/pkg/builtin/#rune 但是我对“符文”一词感到困惑,实际上它代表什么?例如uint == unsigned int 问题答案: 但是我对“符文”一词感到困惑,实际上它代表什么?例如uint == unsigned int 符

  • 我有那段代码(这是从一个更大的项目中复制的最小值)。 它在gcc 9.2中失败,并出现以下错误: 在“constexpr const auto poly_gcd,myint”的实例化中 静态配置的自动val=poly_gcd_reduce_helper::val; 显然,编译器试图实例化poly_gcd_reduce_helper,void 我必须承认我现在不知道该怎么办。

  • 问题内容: 我正在编写一个小脚本,该脚本从目录获取文件名,并将其传递给另一个模块,然后该模块导入文件。 因此流程类似于1)获取模块名称(将其存储在变量中)2)将此变量名称传递给模块3)导入名称存储在变量名称中的模块 我的代码就像 问题在于,当它到达import语句时,它将modulename读取为真实的模块名称,而不是存储在此变量中的值。我不确定这在python中如何工作,解决这个问题的任何帮助都

  • 本文向大家介绍Java变量类型与示例,包括了Java变量类型与示例的使用技巧和注意事项,需要的朋友参考一下 Java变量 变量是用户定义的存储块名称,它们的值可以在程序执行期间随时更改。它们在类/程序中起着重要的作用,因为它们有助于存储,检索数据值。 Java中变量的类型 有三种类型的Java变量, 实例变量 局部变量 类/静态变量 1)实例变量 实例变量在类中声明,但在方法,块或构造函数之外。