如何在Rust中实现侵入式列表?(闭包场景下段错误修复)
用Rust实现Crafting Interpreters中的侵入式开放Upvalue列表
原代码存在几个核心问题导致段错误,逐一拆解并给出正确实现:
核心问题分析
- 局部变量指针悬空:
allocate_upvalue中把栈上临时value的指针存入ObjUpvalue,函数返回后该临时变量销毁,指针变成野指针,直接触发段错误。 - 链表遍历逻辑错误:原C代码是遍历到
upvalue->location > local的节点继续前进,Rust代码中条件写反,且用swap修改指针的方式完全错误,没有正确遍历链表。 - 不必要的内存包装:
closed字段用Box<Value>纯属多余,直接存储Value即可,增加了不必要的内存间接层。 - 指针操作不当:修改链表节点的
next指针时使用swap是错误的,应该直接赋值更新链表关系。
正确实现方案
首先定义基础结构(根据你的Value类型补充完整):
#[derive(Clone)] enum Value { ValNil, ValNumber(f64), ValBool(bool), // 其他Value类型按需添加 } struct ObjUpvalue { // 开放状态:指向VM栈上的Value;关闭状态:指向自身的closed字段 location: *mut Value, // 关闭后存储的备份值 closed: Value, // 侵入式链表的下一个节点指针 next: *mut ObjUpvalue, } struct VM { open_upvalues: *mut ObjUpvalue, // VM的栈,存储局部变量 stack: Vec<Value>, // VM其他字段(如全局变量、当前函数等)按需添加 }
1. 分配Upvalue
直接传入栈上Value的有效指针,避免悬空指针:
impl VM { fn allocate_upvalue(&mut self, location: *mut Value) -> *mut ObjUpvalue { let upvalue = Box::new(ObjUpvalue { location, closed: Value::ValNil, next: std::ptr::null_mut(), }); Box::into_raw(upvalue) }
2. 捕获Upvalue(对应C的captureUpvalue)
严格复刻C的链表遍历和插入逻辑:
fn capture_upvalue(&mut self, local: *mut Value) -> *mut ObjUpvalue { unsafe { let mut prev_upvalue: *mut ObjUpvalue = std::ptr::null_mut(); let mut upvalue = self.open_upvalues; // 遍历链表,找到第一个location <= local的节点(C逻辑:upvalue->location > local时继续) while !upvalue.is_null() && (*upvalue).location > local { prev_upvalue = upvalue; upvalue = (*upvalue).next; } // 找到已存在的upvalue,直接返回 if !upvalue.is_null() && (*upvalue).location == local { return upvalue; } // 创建新upvalue并插入链表的正确位置 let created_upvalue = self.allocate_upvalue(local); (*created_upvalue).next = upvalue; if prev_upvalue.is_null() { self.open_upvalues = created_upvalue; } else { (*prev_upvalue).next = created_upvalue; } created_upvalue } }
3. 关闭Upvalue(对应C的closeUpvalues)
正确处理值的拷贝和指针切换:
fn close_upvalues(&mut self, last: *mut Value) { unsafe { while !self.open_upvalues.is_null() && (*self.open_upvalues).location >= last { let upvalue = self.open_upvalues; // 将栈上的值拷贝到closed字段 (*upvalue).closed = (*(*upvalue).location).clone(); // 将location指向自身的closed字段,完成关闭 (*upvalue).location = &mut (*upvalue).closed as *mut Value; // 从开放列表中移除当前节点 self.open_upvalues = (*upvalue).next; } } } }
关键注意事项
- 裸指针安全:所有裸指针操作必须在
unsafe块中,且要保证指针指向的内存始终有效:- 开放状态下,
location指向VM栈上的Value,栈元素未被弹出时指针有效; - 关闭状态下,
location指向ObjUpvalue自身的closed字段,只要ObjUpvalue未被释放,指针就有效。
- 开放状态下,
- 侵入式链表维护:完全沿用C的逻辑,通过节点自身的
next指针串联,不需要额外的链表容器。 - 值拷贝:关闭Upvalue时必须拷贝栈上的值到
closed字段,避免栈元素销毁后数据丢失。
内容的提问来源于stack exchange,提问作者bbawj
相关产品推荐
相关产品推荐

