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

长期运行语言服务器中基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:02:02