You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust macro_rules宏无法递归展开问题排查

解决结构体构建宏的递归展开问题

原代码

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 {
  name:(String::from("hello")),__hello_field!(age:10)
}

修复方案

问题出在结构体初始化上下文的宏展开限制:Rust解析Hello { ... }内部时,会优先识别字段语法,嵌套的宏调用无法完成递归展开,导致剩余__hello_field!调用被判定为无效。

通过调整hello宏的展开方式,迫使编译器先完成所有递归展开,再生成合法的字段列表:

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)*) => {
        $crate::__hello_field!($field: $value),
        $crate::__hello_field!($($tail)*)
    };
    () => {};
    (,) => {};
}

macro_rules! hello {
    ($($tail:tt)*) => {
        Hello {
            // 使用重复展开器确保宏完全展开后再插入结构体
            $($crate::__hello_field!($($tail)*),)*
        }
    };
}

fn main() {
    let hello_item = hello!(name: String::from("hello"), age: 10);
    // 支持 trailing comma
    let hello_item2 = hello!(name: String::from("world"), age: 20,);
}

关键说明

  1. 强制完全展开:$($crate::__hello_field!($($tail)*),)*中的$()重复展开器会先触发__hello_field的所有递归调用,将结果拼接成完整的字段列表,再注入结构体。
  2. 跨crate兼容性:$crate::前缀确保宏在不同 crate 环境下能正确引用,避免命名冲突。
  3. 保留灵活性:仍支持 trailing comma,提升调用体验。

修复后宏会正确展开为预期的结构体初始化代码。

内容的提问来源于stack exchange,提问作者Nick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:50:56