rust 宏不递归扩展(macro_rules)

kyks70gy  于 5个月前  发布在  Mac
关注(0)|答案(1)|浏览(70)

我试图创建一个宏来帮助构建结构体,但宏似乎没有完全展开,而是还剩下一个迭代。

代码

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);
}

字符串

预期输出

let hello_item = Hello {
    name: (String::from("hello")),
    age: Some(10),
};


这里的空白并不重要。

编译错误

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)

rust-analyzer认为结果的输出

请注意,还有一个__hello_field!宏的示例。

Hello {
  name:(String::from("hello")),__hello_field!(age:10)
}


实际输出由VS Code rust-analyzer扩展中的“Expand macro recursively at caret”特性生成。

参见

fae0ux8s

fae0ux8s1#

Root issue

根本问题是宏是从外到内计算的。这意味着宏的最后一次迭代产生了无效的代码(即,它产生了age: Some(10))。宏必须总是产生完整的,有效的Rust代码(例如整个表达式)。

解决方案

使用下推累加解决了这个问题。这迫使宏从内到外进行计算。

macro_rules! __hello_impl {
    (() -> ($($output:tt)*)) => {
        Hello {
            $($output)*
        }
    };
    ((name: $value:expr, $($rest:tt)*) -> ($($output:tt)*)) => {
        __hello_impl!(($($rest)*) -> ($($output)* name: $value, ))
    };
    ((name: $value:expr) -> ($($output:tt)*)) => {
        __hello_impl!(() -> ($($output)* name: $value ))
    };
    ((age: $value:expr, $($rest:tt)*) -> ($($output:tt)*)) => {
        __hello_impl!(($($rest)*) -> ($($output)* age: Some($value), ))
    };
    ((age: $value:expr) -> ($($output:tt)*)) => {
        __hello_impl!(() -> ($($output)* age: Some($value) ))
    };
}
macro_rules! hello {
    ($($input:tt)*) => {
        {
            __hello_impl!(($($input)*) -> ())
        }
    };
}

字符串

资源

相关问题