长期运行语言服务器中基于Arena的AST所有权建模方案问询
解决Rust中Arena与AST在语言服务器状态的所有权建模问题
你的核心问题是无法在结构体中安全绑定Arena与其引用的AST生命周期,Rust的借用检查器不允许这种直接的自引用结构。下面给出两种可靠的解决方案:
方案一:使用自引用结构体库(安全封装)
借助ouroboros crate可以生成安全的自引用结构体,自动处理Arena与AST的生命周期绑定,无需手动unsafe操作。
步骤1:添加依赖
在Cargo.toml中加入:
ouroboros = "0.15"
步骤2:实现代码
use ouroboros::self_referencing; use typed_arena::Arena; // 保留原有Ty和Expr的定义 enum Ty<'ty> { Int, List(&'ty Ty<'ty>), } enum Expr<'ty> { Literal(isize), List(Vec<Expr<'ty>>), TypeAnnotated(Expr<'ty>, &'ty Ty<'ty>) } fn parse_and_type_check<'ty>(input: String, ty_arena: &'ty Arena<Ty<'ty>>) -> Expr<'ty> { todo!() } // 定义自引用结构体,将Arena和依赖它的Expr绑定 #[self_referencing] struct Program { arena: Arena<Ty<'static>>, #[borrows(arena)] #[covariant] expr: Expr<'this>, } // 语言服务器状态只需持有Program的Option struct LanguageServerState { latest_program: Option<Program>, } fn main() { let mut state = LanguageServerState { latest_program: None }; loop { let new_program_str = "[1, [2, 3]]".to_owned(); // 创建自引用的Program实例 let new_program = Program::new( Arena::new(), |arena| parse_and_type_check(new_program_str, arena), ); // 更新状态:旧Program会被自动丢弃,内存随之回收 state.latest_program = Some(new_program); // 处理LSP请求时,通过borrow_expr访问AST if let Some(program) = &state.latest_program { let current_expr = program.borrow_expr(); // 执行请求处理逻辑 } } }
ouroboros会在编译期确保:只要Program实例存在,内部的Arena就不会被释放,AST的引用永远不会悬空。
方案二:用索引替代引用(无外部依赖)
放弃直接引用,改用usize索引指向Arena中的Ty元素,这样AST无需生命周期标注,可与Arena直接共存于结构体中。
实现代码
use typed_arena::Arena; // Ty改用索引引用其他Ty,无需生命周期 #[derive(Clone, Copy)] enum Ty { Int, List(usize), // 存储Arena中目标Ty的内存偏移索引 } // Expr同样存储索引,无需生命周期 enum Expr { Literal(isize), List(Vec<Expr>), TypeAnnotated(Expr, usize), // 存储Arena中目标Ty的索引 } // 解析函数直接返回填充好的Arena和AST fn parse_and_type_check(input: String) -> (Arena<Ty>, Expr) { let mut arena = Arena::new(); // 示例:创建Int类型并记录其索引 let int_ty = arena.alloc(Ty::Int); let int_ty_idx = int_ty as *const _ as usize; // 这里实现你的解析与类型检查逻辑,生成带索引的Expr let expr = Expr::TypeAnnotated(Expr::Literal(42), int_ty_idx); (arena, expr) } // 语言服务器状态直接持有Arena与Expr的元组 struct LanguageServerState { latest_program: Option<(Arena<Ty>, Expr)>, } fn main() { let mut state = LanguageServerState { latest_program: None }; loop { let new_program_str = "[1, [2, 3]]".to_owned(); // 生成新的Arena和AST let (fresh_arena, new_expr) = parse_and_type_check(new_program_str); // 更新状态,旧数据自动回收 state.latest_program = Some((fresh_arena, new_expr)); // 处理请求时,通过索引从Arena中获取Ty if let Some((arena, expr)) = &state.latest_program { if let Expr::TypeAnnotated(_, ty_idx) = expr { // 由于索引是解析时生成的,可安全转换为引用 let ty = unsafe { &*(*arena as *const Arena<Ty>).offset(*ty_idx as isize) }; // 执行请求处理逻辑 } } } }
这种方式无需外部库,完全依赖typed_arena实现,唯一的注意点是要保证索引的有效性(由解析逻辑自行保证)。
内容的提问来源于stack exchange,提问作者Alec
相关产品推荐
相关产品推荐

