如何在Rust中创建自引用抽象语法树(AST)?
解决Rust中递归AST节点的构造问题
问题背景
我们有一个极简编程语言的AST结构,仅包含函数和调用定义:
use std::sync::Arc; struct Function { pub body: Vec<Call> } struct Call { pub function: Arc<Function> }
尝试实现函数递归调用时,常规写法都会失败:
- 直接初始化时无法引用未创建的
Arc:
let f = Arc::new(Function { body: vec![Call {function: f.clone()}] }) // 报错:使用了未初始化的变量f
- 先创建空
Arc再修改内容也不可行,因为Arc包裹的数据默认不可变:
let mut f = Arc::new(Function { body: vec![] }); f.body.push(Call { function: f.clone() }) // 报错:无法修改Arc中的不可变数据
现有方案是用Arc<RwLock<_>>或Arc<Mutex<_>>包裹Function:
use std::sync::{Arc, RwLock}; struct Call { pub function: Arc<RwLock<Function>> }
创建递归实例的代码如下:
let f = Arc::new(RwLock::new(Function { body: vec![] })); f.write().unwrap().body.push(Call { function: f.clone() })
但这个方案的痛点很明显:即使仅在构造阶段需要可变操作,后续所有访问都必须通过read()/write()加锁解锁,不仅代码冗余,还会带来不必要的运行时开销。
更优解决方案:利用一次性/惰性初始化工具
方案1:使用OnceCell实现延迟初始化
借助OnceCell的一次性初始化特性,先创建空的Arc<OnceCell<Function>>,再填充内容并建立递归引用:
use std::sync::{Arc, OnceCell}; struct Function { pub body: Vec<Call> } struct Call { pub function: Arc<OnceCell<Function>> } // 创建递归函数实例 let f = Arc::new(OnceCell::new()); f.set(Function { body: vec![Call { function: f.clone() }] }).unwrap();
后续访问时,只需调用get().unwrap()即可获取不可变引用,无需每次加锁:
// 访问函数体 let function_body = &f.get().unwrap().body;
方案2:使用LazyLock(Rust 1.63+)实现惰性初始化
如果你的Rust版本在1.63及以上,可以用标准库的LazyLock(原社区库lazy_static的官方实现),写法更简洁:
use std::sync::{Arc, LazyLock}; struct Function { pub body: Vec<Call> } struct Call { pub function: Arc<LazyLock<Function>> } // 创建递归函数实例 let f = Arc::new(LazyLock::new(|| { Function { body: vec![Call { function: f.clone() }] } }));
访问时直接通过&*f即可获取Function的引用,无需额外调用:
let function_body = &(*f).body;
方案优势
- 仅在构造阶段处理可变逻辑,后续访问完全是无锁的不可变引用,性能更优
- 避免了
RwLock/Mutex带来的冗余代码和运行时开销 - 保证数据安全性:
OnceCell和LazyLock都确保初始化只执行一次,且初始化完成后数据不可变
内容的提问来源于stack exchange,提问作者gavrilikhin.d
相关产品推荐
相关产品推荐

