模块路径+类型名称的宏实例中应该使用哪些片段说明符(元变量类型)?
What fragment specifiers (metavariable types) should be used in macro-by-example for a module path + type name?
我发现自己写的代码是这样的:
// 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, )* )
}
}
}
但无论我为元变量(path
、ident
、ty
、tt
、它们的组合)选择什么片段说明符,它都会出错。让这个工作的魔法咒语是什么?
类型 A
和 B
看起来像这样:
// In "foo.rs"
pub mod bar {
pub struct A;
pub struct B;
}
An (erroring) example on the rust playground.
这里当然有一个 XY 问题:我正在使用 diesel 板条箱并且具有带有 Queryable
派生 impl 的结构,我希望能够 .select()
正确的列来填充给定的结构。虽然可能还有其他解决方案,但我仍然想了解为什么我编写的宏不起作用,以及如果有什么会起作用。
你可以用 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,)* )
}
}
};
}
编辑:
简单匹配不起作用的原因是您不能在 Rust 宏中连接路径(没有过程宏的帮助)。这是因为,引用参考文献 (https://doc.rust-lang.org/reference/macros-by-example.html#transcribing):
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens.
不仅在转发到另一个宏时如此,在转发到编译器时也是如此。
您想构建两个不同的 AST 片段:一个 Type
for the return type (each type in the tuple), and an Expression
for the body. That is, crate::foo::bar::A
(for instance) fulfill two roles: in the return type it is a type (specifically TypePath
), and in the body it is an expression (specifically PathExpression
).
如果我们查看两者(TypePath
和 PathExpression
)的定义,我们会发现它们本质上等同于以下内容(忽略不相关的部分,如泛型和函数路径):
路径:
::
? PathIdentSegment (::
PathIdentSegment) *
PathIdentSegment :
IDENTIFIER | super
| self
| Self
| crate
| $crate
如果您不熟悉 EBNF 表示法,这意味着标识符列表(macro_rules!
中的 :ident
),由 ::
分隔。
因此,当您执行以下操作时:
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
节点,只保留原始标识符。我们 可以 连接原始标识符并从中创建一个 Path
(这通常是使用 tt-munchers 的原因:当我们不需要使用 Rust 宏的语法片段捕获能力时因为我们想在之后恢复它们)。
我发现自己写的代码是这样的:
// 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, )* )
}
}
}
但无论我为元变量(path
、ident
、ty
、tt
、它们的组合)选择什么片段说明符,它都会出错。让这个工作的魔法咒语是什么?
类型 A
和 B
看起来像这样:
// In "foo.rs"
pub mod bar {
pub struct A;
pub struct B;
}
An (erroring) example on the rust playground.
这里当然有一个 XY 问题:我正在使用 diesel 板条箱并且具有带有 Queryable
派生 impl 的结构,我希望能够 .select()
正确的列来填充给定的结构。虽然可能还有其他解决方案,但我仍然想了解为什么我编写的宏不起作用,以及如果有什么会起作用。
你可以用 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,)* )
}
}
};
}
编辑:
简单匹配不起作用的原因是您不能在 Rust 宏中连接路径(没有过程宏的帮助)。这是因为,引用参考文献 (https://doc.rust-lang.org/reference/macros-by-example.html#transcribing):
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens.
不仅在转发到另一个宏时如此,在转发到编译器时也是如此。
您想构建两个不同的 AST 片段:一个 Type
for the return type (each type in the tuple), and an Expression
for the body. That is, crate::foo::bar::A
(for instance) fulfill two roles: in the return type it is a type (specifically TypePath
), and in the body it is an expression (specifically PathExpression
).
如果我们查看两者(TypePath
和 PathExpression
)的定义,我们会发现它们本质上等同于以下内容(忽略不相关的部分,如泛型和函数路径):
路径:
::
? PathIdentSegment (::
PathIdentSegment) *
PathIdentSegment :
IDENTIFIER | super
| self
| Self
| crate
| $crate
如果您不熟悉 EBNF 表示法,这意味着标识符列表(macro_rules!
中的 :ident
),由 ::
分隔。
因此,当您执行以下操作时:
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
节点,只保留原始标识符。我们 可以 连接原始标识符并从中创建一个 Path
(这通常是使用 tt-munchers 的原因:当我们不需要使用 Rust 宏的语法片段捕获能力时因为我们想在之后恢复它们)。