将嵌套结构字段路径作为宏参数传递
Passing nested struct field path as macro parameter
我是 Rust 的新手,我无法编译以下代码:
#![feature(trace_macros)]
fn main() {
#[derive(Debug)]
struct Inner {
value: u8
}
#[derive(Debug)]
struct Outer {
inner: Inner
}
let mut x = Outer { inner: Inner { value: 64 } };
/********/
macro_rules! my_macro {
($field_path:expr, $v:expr) => {
x.$field_path = $v;
}
}
trace_macros!(true);
// my_macro!(inner, Inner { value: 42 }); // only works with $field_path:ident
my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
trace_macros!(false);
x . inner.value = 42; // works fine
assert_eq!(42, x.inner.value);
}
我收到以下错误:
error: unexpected token: `inner.value`
--> src/main.rs:20:15
|
20 | x.$field_path = $v;
| ^^^^^^^^^^^
...
26 | my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
| --------------------------- in this macro invocation
|
...
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `inner.value`
--> src/main.rs:20:15
|
20 | x.$field_path = $v;
| ^^^^^^^^^^^ expected one of `.`, `;`, `?`, `}`, or an operator
...
26 | my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
| --------------------------- in this macro invocation
|
...
不过trace_macro
好像可以展开my_macro!
:
note: trace_macro
--> src/main.rs:26:5
|
26 | my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: expanding `my_macro! { inner . value, 42 }`
= note: to `x . inner.value = 42 ;` <<< exactly what I am looking for
如果我将 $field_path
参数保留为 ident
,我只是得到 no rules expected the token `.`
,我认为这是有道理的,因为 .
是一个运算符。
我错过了什么?
I think the issue lies in the fact that inner.value
contains the dot operator.
这正是你的问题。没有一个 fragment specifiers that allows you to match ident
and/or a field access expression. The issue with using expr
(expressions) 是在扩展时它基本上包含在括号中,即 x.(inner.value)
,这没有意义,因此你得到 "unexpected token `inner.value`"
的原因。
不过,你确实可以使用ident
,你只需要使用repetition即可。
简而言之,您可以 $( $field_path:ident ).+
而不是 $field_path:ident
。然后扩展它,而不是 x.$field_path
然后你做 x. $( $field_path ).+
.
macro_rules! my_macro {
($($field_path:ident).+, $v:expr) => {
x.$($field_path).+ = $v;
};
}
// Now both are allowed
my_macro!(inner.value, 42);
my_macro!(inner, Inner { value: 64 });
它不起作用的原因是 Rust 宏在 AST 节点而不是标记上运行。
用于访问嵌套字段的 AST 如下所示(语法非常松散,其中括号表示 AST 节点):(((x).outer).inner)
您已将 $field_path
设置为 expr
,因此它会像这样解析:((inner).outer)
。这是访问 inner
变量的 outer
字段的有效表达式。
然后,评估宏会产生如下内容:((x).((inner).outer))
,这没有意义。
现在,如何解决?
关键是变量名(如本例中的 x
)需要在访问该字段的表达式的 AST“深处”结束。这使得无法通过任何方式将路径作为宏参数“传入”。
你可以做的是传入一个构建路径表达式的宏:
macro_rules! my_macro {
($path:ident, $v:expr) => {
$path!(x) = $v;
}
}
macro_rules! inner_value {
($x:ident) => {
$x.inner.value
}
}
my_macro!(inner_value, 42); // expected output: x.inner.value = 42;
不过,为了更好的可读性,我会考虑在没有宏的情况下执行此操作。根据您的操作,使用闭包可能有效:
fn do_things(x: &mut Outer, path: impl Fn(&mut Outer) -> &mut u8){
*(path(x)) = 42;
}
do_things(&mut x, |x| &mut x.inner.value);
我是 Rust 的新手,我无法编译以下代码:
#![feature(trace_macros)]
fn main() {
#[derive(Debug)]
struct Inner {
value: u8
}
#[derive(Debug)]
struct Outer {
inner: Inner
}
let mut x = Outer { inner: Inner { value: 64 } };
/********/
macro_rules! my_macro {
($field_path:expr, $v:expr) => {
x.$field_path = $v;
}
}
trace_macros!(true);
// my_macro!(inner, Inner { value: 42 }); // only works with $field_path:ident
my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
trace_macros!(false);
x . inner.value = 42; // works fine
assert_eq!(42, x.inner.value);
}
我收到以下错误:
error: unexpected token: `inner.value`
--> src/main.rs:20:15
|
20 | x.$field_path = $v;
| ^^^^^^^^^^^
...
26 | my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
| --------------------------- in this macro invocation
|
...
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `inner.value`
--> src/main.rs:20:15
|
20 | x.$field_path = $v;
| ^^^^^^^^^^^ expected one of `.`, `;`, `?`, `}`, or an operator
...
26 | my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
| --------------------------- in this macro invocation
|
...
不过trace_macro
好像可以展开my_macro!
:
note: trace_macro
--> src/main.rs:26:5
|
26 | my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: expanding `my_macro! { inner . value, 42 }`
= note: to `x . inner.value = 42 ;` <<< exactly what I am looking for
如果我将 $field_path
参数保留为 ident
,我只是得到 no rules expected the token `.`
,我认为这是有道理的,因为 .
是一个运算符。
我错过了什么?
I think the issue lies in the fact that
inner.value
contains the dot operator.
这正是你的问题。没有一个 fragment specifiers that allows you to match ident
and/or a field access expression. The issue with using expr
(expressions) 是在扩展时它基本上包含在括号中,即 x.(inner.value)
,这没有意义,因此你得到 "unexpected token `inner.value`"
的原因。
不过,你确实可以使用ident
,你只需要使用repetition即可。
简而言之,您可以 $( $field_path:ident ).+
而不是 $field_path:ident
。然后扩展它,而不是 x.$field_path
然后你做 x. $( $field_path ).+
.
macro_rules! my_macro {
($($field_path:ident).+, $v:expr) => {
x.$($field_path).+ = $v;
};
}
// Now both are allowed
my_macro!(inner.value, 42);
my_macro!(inner, Inner { value: 64 });
它不起作用的原因是 Rust 宏在 AST 节点而不是标记上运行。
用于访问嵌套字段的 AST 如下所示(语法非常松散,其中括号表示 AST 节点):(((x).outer).inner)
您已将 $field_path
设置为 expr
,因此它会像这样解析:((inner).outer)
。这是访问 inner
变量的 outer
字段的有效表达式。
然后,评估宏会产生如下内容:((x).((inner).outer))
,这没有意义。
现在,如何解决?
关键是变量名(如本例中的 x
)需要在访问该字段的表达式的 AST“深处”结束。这使得无法通过任何方式将路径作为宏参数“传入”。
你可以做的是传入一个构建路径表达式的宏:
macro_rules! my_macro {
($path:ident, $v:expr) => {
$path!(x) = $v;
}
}
macro_rules! inner_value {
($x:ident) => {
$x.inner.value
}
}
my_macro!(inner_value, 42); // expected output: x.inner.value = 42;
不过,为了更好的可读性,我会考虑在没有宏的情况下执行此操作。根据您的操作,使用闭包可能有效:
fn do_things(x: &mut Outer, path: impl Fn(&mut Outer) -> &mut u8){
*(path(x)) = 42;
}
do_things(&mut x, |x| &mut x.inner.value);