Rust中self相关借用错误的困惑及解决方案咨询
简化代码示例(MRE)
use std::collections::HashMap; fn main() { let mut framework = Framework { index_list: Vec::new(), }; framework.method_one(); } #[derive(Debug)] struct Framework { index_list: Vec<usize>, } #[derive(Debug)] struct Doc<'a> { framework: &'a Framework, } impl Framework { fn method_one(&mut self){ println!("method one"); let index_list: Vec<usize>; let (doc_map, index_list) = self.get_map_and_list(); println!("map {:#?}, list {:#?}", doc_map, self.index_list); self.index_list = index_list; // uncommenting this causes the issue: // self.manipulate_list_more(doc_map); } fn get_map_and_list(&self) -> (HashMap<String, Doc>, Vec<usize>) { let mut doc_map = HashMap::new(); doc_map.insert("bubble".to_string(), Doc{framework: &self}); let mut index_list: Vec<usize> = Vec::new(); index_list.push(99); (doc_map, index_list) } fn manipulate_list_more(&mut self, doc_map: HashMap<String, Doc>) { // NB whether this is "&self" or "&mut self" is irrelevant ... println!("submethod_two"); // even if this is commented out we still get the error... // showing that the commands in this method are irrelevant to the problem involved // self.index_list.push(100); } }
问题背景
这是业务代码的简化版本,Doc必须持有Framework的引用,因此使用了生命周期参数。原本希望将map和list都作为Framework的字段,但Framework是pyclass,不允许携带生命周期参数,所以只能采用当前结构。取消注释self.manipulate_list_more(doc_map)后,触发以下借用检查错误:
error[E0506]: cannot assign to `self.index_list` because it is borrowed --> src\main.rs:28:3 | 26 | let (doc_map, index_list) = self.get_map_and_list(); | ----------------------- `self.index_list` is borrowed here 27 | println!("map {:#?}, list {:#?}", doc_map, self.index_list); 28 | self.index_list = index_list; | ^^^^^^^^^^^^^^^ `self.index_list` is assigned to here but it was already borrowed 29 | self.manipulate_list_more(doc_map); | ------- borrow later used here
疑问解答
1. 为什么从get_map_and_list返回后,self的借用并未结束?
get_map_and_list返回的HashMap<String, Doc>中,每个Doc实例都持有&self的不可变引用。根据Rust的生命周期规则,这个引用的生命周期与get_map_and_list的&self参数绑定——只要doc_map还处于作用域内(即还被使用),self的不可变借用就会持续生效。
2. 注释掉self.manipulate_list_more(doc_map)后,编译器允许对self.index_list赋值的原因?
这是Rust 非 lexical lifetimes(NLL) 优化的结果。当你不把doc_map传给后续方法时,编译器能分析出doc_map在执行self.index_list = index_list之前已经不再被使用(唯一的使用是之前的println!),因此会提前结束self的不可变借用,允许后续对self.index_list的可变操作(赋值需要可变借用)。但如果要将doc_map传入manipulate_list_more,说明doc_map在赋值之后仍会被使用,借用无法提前结束,可变借用和之前的不可变借用就会产生冲突,触发报错。
可行解决方案
结合Framework是pyclass、不能携带生命周期的限制,推荐以下几种方案:
方案1:使用Rc<RefCell<Framework>>实现内部可变性
将Framework包裹在Rc<RefCell>中,让Doc持有Rc<RefCell<Framework>>,这样既不需要生命周期参数,又能实现对Framework的内部可变访问,同时满足Doc持有Framework引用的需求:
use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; fn main() { let framework = Rc::new(RefCell::new(Framework { index_list: Vec::new(), })); framework.borrow_mut().method_one(Rc::clone(&framework)); } #[derive(Debug)] struct Framework { index_list: Vec<usize>, } #[derive(Debug)] struct Doc { framework: Rc<RefCell<Framework>>, } impl Framework { fn method_one(&mut self, framework_rc: Rc<RefCell<Framework>>){ println!("method one"); let (doc_map, index_list) = self.get_map_and_list(framework_rc); println!("map {:#?}, list {:#?}", doc_map, self.index_list); self.index_list = index_list; self.manipulate_list_more(doc_map); } fn get_map_and_list(&self, framework_rc: Rc<RefCell<Framework>>) -> (HashMap<String, Doc>, Vec<usize>) { let mut doc_map = HashMap::new(); doc_map.insert("bubble".to_string(), Doc{framework: framework_rc}); let mut index_list: Vec<usize> = Vec::new(); index_list.push(99); (doc_map, index_list) } fn manipulate_list_more(&mut self, doc_map: HashMap<String, Doc>) { println!("submethod_two"); // 可以安全修改index_list self.index_list.push(100); } }
注意:在Python绑定场景中,需要确保Rc和RefCell与Python的GIL兼容,通常可以通过pyo3的相关工具处理。
方案2:使用Weak<RefCell<Framework>>避免循环引用
如果Doc不需要始终持有Framework的强引用,可以改用Weak指针,防止潜在的循环引用问题:
use std::collections::HashMap; use std::rc::{Rc, Weak}; use std::cell::RefCell; #[derive(Debug)] struct Doc { framework: Weak<RefCell<Framework>>, } // 其余代码类似方案1,get_map_and_list中传入Weak::clone(&framework_weak)即可
使用时需要通过upgrade()方法将Weak转为Rc,注意处理None的情况(如果Framework已被销毁)。
方案3:重构数据依赖(业务允许时)
如果业务逻辑允许,将Doc需要的Framework数据单独提取出来,让Doc持有具体数据而非整个Framework的引用,从根源上避免生命周期冲突。比如:
#[derive(Debug)] struct Doc { // 只保存需要的字段,比如index_list的快照或特定数据 some_data: Vec<usize>, }
这种方案最简洁,但需要根据实际业务场景评估可行性。
内容的提问来源于stack exchange,提问作者mike rodent

