I'm trying to create a macro to help build structs but the macro does not seem to be fully expanding. Rather, it has one iteration left.
Code
struct Hello {
name: String,
age: Option<u32>,
}
macro_rules! __hello_field {
(name: $value:expr) => {
name: $value
};
(age: $value:expr) => {
age: Some($value)
};
($field:ident: $value:expr, $($tail:tt)*) => {
__hello_field!($field: $value),
__hello_field!($($tail)*)
};
() => {};
(,) => {};
}
macro_rules! hello {
($($tail:tt)*) => {
Hello {
__hello_field!($($tail)*)
}
};
}
fn main() {
let hello_item = hello!(name: String::from("hello"), age: 10);
}
Expected output
let hello_item = Hello {
name: (String::from("hello")),
age: Some(10),
};
Whitespace is not important here.
Compile error produced
error: expected one of `,`, `:`, or `}`, found `!`
--> src/main.rs:24:26
|
23 | Hello {
| ----- while parsing this struct
24 | __hello_field!($($tail)*)
| ^ expected one of `,`, `:`, or `}`
...
30 | let hello_item = hello!(name: String::from("hello"), age: 10);
| -------------------------------------------- in this macro invocation
|
= note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected one of `,`, `.`, `?`, `}`, or an operator, found `)`
--> src/main.rs:24:37
|
23 | Hello {
| ----- while parsing this struct
24 | __hello_field!($($tail)*)
| ^ expected one of `,`, `.`, `?`, `}`, or an operator
...
30 | let hello_item = hello!(name: String::from("hello"), age: 10);
| --------------------------------------------
| | |
| | help: try adding a comma: `,`
| in this macro invocation
|
= note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0063]: missing field `name` in initializer of `Hello`
--> src/main.rs:23:9
|
23 | Hello {
| ^^^^^ missing `name`
...
30 | let hello_item = hello!(name: String::from("hello"), age: 10);
| -------------------------------------------- in this macro invocation
|
= note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info)
Output which rust-analyzer thinks this results in
Notice how there's still one more instance of the __hello_field!
macro.
Hello {
name:(String::from("hello")),__hello_field!(age:10)
}
Actual output is generated by the "Expand macro recursively at caret" feature in the VS Code rust-analyzer extension.
Root issue
The root issue is that the macro was evaluated from the outside inwards. This meant the last iteration of the macro produced invalid code (i.e., it produced
age: Some(10)
). Macros must always produce complete, valid Rust code (such as an entire expression).Solution
Using push-down accumulation fixed the issue. This forces the macro to be evaluated from the inside out.
Resources