如何使用Rust过程宏实现编译期注册的零开销事件系统
需求背景
我正在尝试使用Rust的过程宏实现一套在编译期完成事件与监听器列表构建的事件系统,目标是完全规避运行时注册逻辑,满足事件每秒数百至数千次调用的高性能要求。
目前已完成两个过程宏属性的骨架代码:listener与event,代码如下:
#[proc_macro_attribute] pub fn listener(_attributes: TokenStream, item: TokenStream) -> TokenStream { // 该属性标注在函数上 // 逻辑为将函数名写入临时文件 } #[proc_macro_attribute] pub fn event(_attributes: TokenStream, item: TokenStream) -> TokenStream { // 待实现逻辑 }
预期使用效果
设计目标是通过这两个过程宏实现如下开发体验:
#[listener] pub fn someListener(event: &EventA) { ... } #[listener] pub fn anotherListener(event: &EventA) { ... } #[event] pub struct EventA { ... }
上述业务代码需要自动为EventA生成如下实现:
impl EventA { pub fn call(&self) { someListener(&self); anotherListener(&self); } }
生成的代码可直接按如下方式调用:
fn foo() { let event = EventA { ... }; event.call(); // 自动将&self传入someListener和anotherListener }
已完成的调研与实现
我已经调研了Rust多过程宏调用间存储状态的相关方案,确认可通过临时文件存储过程宏的中间运行状态*,目前已经实现了基础收集逻辑:采集所有标注#[listener]的函数,通过解析函数入参判断监听器对应的监听事件类型。
当前核心阻塞问题
负责为事件类型生成call方法的event宏,必须在所有listener宏执行完成后运行,才能获取到该事件对应的全部监听器列表,但Rust没有提供直接控制过程宏执行顺序的能力。
为此我设计了变通方案:引入额外crate,通过依赖关系保证其在所有过程宏执行完成后再进行构建,crate依赖层级设计如下:
eventcrate:定义listener和event两个过程宏maincrate:编写业务逻辑,使用上述两个宏标注事件结构体与监听器函数event_buildcrate:读取main crate编译过程中生成的监听器、事件列表,为事件类型生成call方法的具体实现
由于event_build crate的构建顺序在main crate之后,我原本认为为其配置build.rs构建脚本后,该脚本会在main crate编译完成(即所有listener过程宏执行完毕)后运行,此时即可拿到完整的监听器列表。
目前该部分逻辑已基本调试通,但遇到了新的链路问题:如何将监听器函数与event.call()方法关联。main crate既需要定义所有监听器,也需要在业务逻辑中调用event.call()方法,但该方法的实现需要等到event_build crate构建阶段才能生成,因为只有此时才能收集到全部监听器列表。
我有C开发背景,最初的思路是借鉴C的前向声明机制:如果能在main crate中声明event.call()函数,在event_build crate中提供该函数的具体实现,即可通过链接器解析符号依赖完成关联。但实践中遇到了阻碍:Rust的extern关键字主要用于FFI场景(即Rust与C等其他语言的互操作),我不清楚当函数定义所在crate需要作为函数声明(使用)方crate的依赖时,如何通过extern关键字完成Rust crate间的函数符号链接。
我能想到的另一个迂回方案是:在event crate中声明extern "C"函数(例如extern_eventa_call),在event_build crate中用C语言实现该函数,函数内部仅转发调用event_build crate中生成的event.call()实现,示例代码如下:
extern void impl_eventa_call(); // C中声明,由Rust的event_build crate定义 void extern_eventa_call() { // Rust的event crate中声明,本处C代码提供定义 impl_eventa_call(); }
我判断该方案可以运行,但实现链路过于复杂冗余。
咨询问题
- 是否有更简洁的方式在Rust中实现事件/监听器模式,同时保证极低甚至无运行时开销?
- 如果没有更优方案,我当前设计的多crate实现路径是否存在潜在问题?
*注:过程宏间通过临时文件存储状态的方案会存在增量编译兼容问题(增量编译场景下,若过程宏标注的代码项无需重编译,对应过程宏不会被重新执行),目前暂不处理该问题。
内容的提问来源于stack exchange,提问作者User12345

