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

如何用声明式Rust宏遍历AST提取文档字符串?是否应改用过程宏?

问题描述

我想编写一个宏,接收代码块并展开,同时收集其中的文档字符串(利用文档字符串是属性的特性)。例如输入:

doc_grab! {
    /// Struct A
    struct A {
        /// Field x
        x: u32,
    }
}

预期展开结果:

const A_DOC: ... = ... "Struct A"
                   ... "Field x" ...;

[doc = "Struct A"]
struct A {
    [doc = "Field x"]
    x: u32,
}

我知道过程宏能实现这类功能(比如Clap的derive API),但想确认:能不能用声明式宏实现?还是更适合用过程宏?

我尝试写了一个简单的walker!宏想递归遍历结构,但运行报错:

macro_rules! walker {
    () => {};
    ($body:tt $($rest:tt)*) => {
        $body walker!($($rest)*)
    };
}

walker! {
    fn get_three(x): u8 {
        1 + 2
    }
}

错误信息:

error: expected one of `(` or `<`, found `!`
  --> src/main.rs:60:21
   |
60 |           $body walker!($($rest)*)
   |                       ^ expected one of `(` or `<`
...
64 | / walker! {
65 | |     fn get_three(x): u8 {
66 | |         1 + 2
67 | |     }
68 | | }
   | |_- in this macro invocation
   |
   = note: this error originates in the macro `walker` (in Nightly builds, run with -Z macro-backtrace for more info)

通过rust-analyzer看到,宏展开第一个fn后就无法处理后续语法:

// Recursive expansion of walker! macro
// =====================================

fn walker
!(get_three(x):u8 {
  1+2
})

如果为每个核心语法写分支,遇到其他宏时会变得异常复杂。

所以我想知道:

  1. 声明式宏能不能遍历语法树?
  2. 是不是更适合改用过程宏?
  3. 如果用过程宏,解析展开后语法树的最佳方式是什么(类似Racket的local-expand,但Rust里没找到类似功能)?
解答

1. 声明式宏无法可靠遍历语法树

声明式宏(macro_rules!)基于token匹配工作,它不理解Rust的语法结构,只能按token序列做模式匹配。你写的walker!宏报错的原因是:它把fn当成单个tt(token tree)输出后直接拼接walker!(...),导致Rust语法解析器把fn walker当成一个标识符,后面的!就成了语法错误——fn定义后不能直接跟宏调用。

哪怕为每个语法结构(struct、fn、字段等)单独写匹配分支,也会遇到两个致命问题:

  • 无法处理嵌套宏调用:声明式宏是先展开外层再处理内层,若代码块里有其他宏,根本无法提前预知其展开后的token,自然没法匹配。
  • 维护成本极高:Rust语法复杂,要覆盖所有合法语法场景(泛型、属性、嵌套表达式等)几乎不可能,每加一个场景就要新增大量分支。

因此,用声明式宏实现完整的文档字符串收集+代码展开完全不现实。

2. 优先选择过程宏实现

过程宏(尤其是类函数过程宏)是处理这类需求的正确选择,原因如下:

  • 过程宏能拿到完整的语法抽象树(AST):通过syn库可直接将输入代码解析为结构化的AST节点,能准确识别文档属性(#[doc]或///)、结构体、字段等元素。
  • 可灵活修改和生成代码:用quote库可基于修改后的AST生成符合语法的输出代码,完美实现“收集文档字符串生成常量+保留原代码带文档属性”的需求。

3. 过程宏解析语法树的最佳实践

Rust生态有成熟工具链处理过程宏的AST解析与代码生成:

  • syn库:负责将输入token流解析为强类型AST节点,支持解析几乎所有Rust语法,包括文档属性、结构体、函数、表达式等。你可以通过syn::Attribute提取文档字符串,遍历结构体字段获取各自的文档。
  • quote库:负责将AST节点或手动构造的代码片段转换为可编译的token流,语法与Rust代码几乎一致,编写直观。
  • 关于类似Racket local-expand的功能:Rust过程宏在编译的语法分析阶段运行,编译器会先展开所有内层宏,再把最终token流传给过程宏处理。若需处理未展开的宏,syn也支持解析宏调用节点,可选择保留或做特殊处理。

简单实现思路(基于syn和quote):

  1. 用syn::parse_macro_input!将输入解析为对应项的结构体,比如syn::ItemStruct。
  2. 遍历结构体属性,提取#[doc]属性内容并收集到常量中。
  3. 遍历结构体字段,提取每个字段的#[doc]属性内容,也加入常量。
  4. 用quote生成包含文档常量和原结构体(保留文档属性)的代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:03:33