首页 > 解决方案 > Rust 递归宏不适用于生成结构

问题描述

我正在尝试编写一个在 Rust 中生成结构的宏。该宏将根据字段类型为结构字段添加不同的 Serde 属性。这是最终目标。

现在,我只是尝试编写一个宏,它使用另一个宏来生成递归代码。

这是代码的样子:

macro_rules! f_list {
    ($fname: ident, $ftype: ty) => {
        pub $fname: $ftype,
    }
}

macro_rules! mk_str {
    ($sname: ident; $($fname: ident: $ftype: ty,)+) => {
        #[derive(Debug, Clone)]
        pub struct $sname {
            $(
                f_list!($fname, $ftype)
            )+
        }
    }
}

mk_str! {
    Yo;
    name: String,
}

fn main() {
    println!("{:?}", Yo { name: "yo".to_string() })
}

运行时的此代码给出以下错误,我无法理解。

error: expected `:`, found `!`
  --> src/main.rs:12:23
   |
12 |                   f_list!($fname, $ftype);
   |                         ^ expected `:`

这里有什么问题?

这是游乐场链接

标签: rustmacrosrules

解决方案


在编写声明性宏 ( macro_rules!) 时,重要的是要了解宏的输出必须是模式、语句、表达式、项或impl. 实际上,从语法上讲,您应该将输出视为可以独立存在的东西。

在您的代码中,如果该宏f_list有效,它将输出如下代码

name1: type1,
name2: type2,
name3: type3,

虽然这可能是结构声明的一部分,但它本身并不是可以独立存在的东西。

那么为什么另一个宏中的错误呢?mk_str成功扩展到

#[derive(Debug, Clone)]
pub struct Yo {
    f_list!(name, String)
}

但是,解析器并不期望结构声明中包含宏。结构的内部不是模式、语句、表达式、项目或impl. 因此,当它看到 时!,它放弃并报告错误。

你怎么能解决这个问题?在这个特定的例子中,f_list是相当多余的。你可以简单地f_list!($fname, $ftype)pub $fname: $ftype,in替换mk_str它,它会像写的那样工作。如果这对您的目标不起作用,请查看The Little Book of Rust Macros。它有一些模式可以用宏做非常复杂的事情。此答案中的大部分信息来自“AST 中的宏”部分。


推荐阅读