你可以通过 tt-munching 来做到这一点:
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,)* )
}
}
};
}
.
Edit:
简单匹配不起作用的原因是您无法在 Rust 宏中连接路径(没有过程宏的帮助)。这是因为,引用参考文献 (https://doc.rust-lang.org/reference/macros-by-example.html#transcribing https://doc.rust-lang.org/reference/macros-by-example.html#transcribing):
当将匹配的片段转发到另一个宏时,第二个宏中的匹配器将看到片段类型的不透明 AST。第二个宏不能使用文字标记来匹配匹配器中的片段,只能使用相同类型的片段说明符。 ident、lifetime 和 tt 片段类型是一个例外,可以通过文字标记进行匹配。
不仅在转发到另一个宏时如此,而且在转发到编译器时也是如此。
您想要构建两个不同的 AST 片段:Type https://doc.rust-lang.org/reference/types.html#type-expressions返回类型(元组中的每种类型),以及Expression https://doc.rust-lang.org/reference/expressions.html为了身体。那是,crate::foo::bar::A
(例如)履行两个角色:在返回类型中它是一个类型(具体来说TypePath https://doc.rust-lang.org/reference/paths.html#paths-in-types),在正文中它是一个表达式(具体来说PathExpression https://doc.rust-lang.org/reference/expressions/path-expr.html).
如果我们看一下两者的定义(TypePath
and PathExpression
),我们看到它们本质上等于以下内容(忽略不相关的部分,如泛型和函数路径):
Path :
::
? PathIdentSegment (::
PathIdentSegment)*
路径识别段 :
识别码 https://doc.rust-lang.org/reference/identifiers.html | super
| self
| Self
| crate
| $crate
如果您不熟悉 EBNF 表示法,这意味着标识符列表 (:ident
in macro_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
只能包含PathIdentSegment
而不是其他Path
! (注意:这不是确切的过程,但或多或少是相同的)。
现在你也明白了为什么 tt-munching 解决方案有效:在那里,我们从不创建一个Path
节点,只保留原始标识符。我们can连接原始标识符并创建一个Path
来自它们(这通常是使用 tt-munchers 的原因:当我们不需要使用 Rust 宏语法片段捕获功能时,因为我们想在之后恢复它们)。