在 Rust 中要实现“发现类并生成动态注册代码”,做法与 Go 的ast包思路不同。Go 是在运行时解析源码,而 Rust 则利用其强大的编译时代码生成能力来实现。
这里有两种主流方式,可以分别满足“灵活发现”和“自动注册”的需求。
🛠️ 方式一:自己解析源码(类似 Go AST 的思路)
如果希望在编译前扫描源文件、提取类型信息并生成代码,可以组合使用下面几个库,构建一套自己的代码生成工具链:
解析源文件 (
syn): 这是 Rust 生态中最流行的解析库,用于将 Rust 源码解析成抽象语法树(AST)。你可以在build.rs脚本中使用它,读取并分析指定目录下的所有.rs文件,找出所有符合条件(例如带有特定注解)的结构体或枚举。从结构体提取信息 (
quote): 配合syn使用,quote库能帮你把提取到的信息拼接成新的 Rust 代码。这是生成注册代码的核心工具。在编译时自动运行: 将上述逻辑放在项目的
build.rs文件中。这样,每次执行cargo build时,你的代码生成逻辑就会先运行,然后生成的代码会被编译进最终的程序里。
社区中有不少项目都采用了这个思路,例如dust使用类似方式读取 Dart 注解并生成代码,rust-ontogen也使用build.rs解析并生成完整的服务端代码。bevy_reflect的派生宏 (#[derive(Reflect)]) 在编译时进行的反射信息生成,也与此类似。
🎯 方式二:用“注册器”实现自动注册(更符合 Rust 习惯)
另一种方法不是去“发现”类型,而是依赖开发者通过属性宏(Attribute Macro)在定义类型时就声明“我要注册”,宏会自动生成注册代码。这种方式更接近“在编译时收集注册信息”的思路。
核心机制:使用类似
#[inventory](来自interoptopus等库)的模式。当你为结构体或函数标注#[ffi]或#[service]这样的宏时,这个宏会为你的类型生成一个“注册器”实现。如何使用:你不需要手动去扫描源码,只需要在定义类型的地方加上宏,然后在统一入口(比如一个
inventory()函数)中,通过register!(MyType)这样的宏调用,将所有带有标记的类型“收集”起来。bevy_reflect的TypeRegistry也提供了类似的运行时注册方式,允许你通过register::<MyStruct>()来手动注册类型。
💡 如何选择?
如果你需要处理未标记的、或第三方 crate 中的类型,那么方式一(使用
syn+build.rs自己解析)是唯一的选择,因为只有扫描源码才能“发现”这些未知类型。如果你只想为自己的代码中的类型生成注册逻辑,那么方式二(使用属性宏)是更简单、更符合 Rust 习惯的做法。它利用宏系统在编译时“知道”你的类型,并自动为其生成注册代码,省去了手动解析的麻烦。
🧩 方案对比速览
| 对比维度 | 自行解析源码 (build.rs + syn) | 使用属性宏 (如 interoptopus/inventory) |
|---|---|---|
| 核心库 | syn,quote | 项目提供的属性宏 (如#[ffi]) |
| 工作原理 | 在build.rs中扫描、解析源码文件,生成 Rust 代码文件。 | 宏在编译时处理类型定义,生成对应的注册代码。 |
| 能否发现第三方类型 | ✅ 是,可以解析外部 crate 的源码。 | ❌ 否,只能处理被自身宏标记的类型。 |
| 使用复杂度 | 较高,需要处理文件 I/O 和 AST 解析。 | 较低,只需为类型添加注解并在入口处汇总调用。 |
| 典型应用 | dust,rust-ontogen | interoptopus |
你是打算为项目中的自定义类型生成注册代码,还是需要处理外部 crate 中的类型?如果方便说下具体场景,我可以帮你看看更贴合的例子。