为何RefCell的作用域规则与普通引用不同?
Rust中RefCell借用的主动归还问题
先看这段能正常编译的代码:
use std::collections::HashMap; struct A { data: HashMap<u32, B>, } struct B { val: Option<u32>, } impl A { fn f(&mut self, key: u32) { let data = &self.data[&key]; match data.val { Some(value) => panic!("{}", value), None => self.data.remove(&key), }; } }
这里先以不可变方式借用&self.data[&key],之后又可变调用self.data.remove(&key),编译器允许该操作的原因是data在可变借用后不再被使用,静态借用检查能识别这一点。
但将普通引用替换为RefCell后,逻辑看似一致却出现编译错误:
use std::collections::HashMap; use std::cell::RefCell; struct A { data: HashMap<u32, RefCell<B>>, } struct B { val: Option<u32>, } impl A { fn f(&mut self, key: u32) { let data = self.data[&key].borrow(); match data.val { Some(value) => panic!("{}", value), None => self.data.remove(&key), }; } }
错误信息如下:
error[E0502]: cannot borrow `self.data` as mutable because it is also borrowed as immutable --> src/main.rs:17:21 | 14 | let data = self.data[&key].borrow(); | --------- immutable borrow occurs here ... 17 | None => self.data.remove(&key), | ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here 18 | }; 19 | } | - immutable borrow might be used here, when `data` is dropped and runs the destructor for type `Ref<'_, B>`
确实如你猜测的那样:RefCell的借用检查在运行时执行,编译器无法静态判断data在None分支里已经不再被使用,它只能识别到data的作用域覆盖整个match语句,因此认为可变借用与不可变借用存在冲突。
解决方法:主动提前归还借用
有两种常用方式可以主动结束Ref的生命周期,让借用提前归还:
- 用代码块限制作用域
将获取Ref和检查val的逻辑放到单独的花括号中,data会在花括号结束时被自动销毁,借用随之归还,后续调用remove就不会触发冲突:
impl A { fn f(&mut self, key: u32) { let should_remove = { let data = self.data[&key].borrow(); data.val.is_none() }; if should_remove { self.data.remove(&key); } else { let value = self.data[&key].borrow().val.unwrap(); panic!("{}", value); } } }
- 手动调用
drop
在None分支里手动调用drop(data),立即销毁Ref实例并释放RefCell的不可变借用,之后即可安全地对self.data进行可变操作:
impl A { fn f(&mut self, key: u32) { let data = self.data[&key].borrow(); match data.val { Some(value) => panic!("{}", value), None => { drop(data); // 主动归还借用 self.data.remove(&key); } }; } }
这两种方法都能让编译器明确感知到借用已提前结束,从而顺利通过编译。
内容的提问来源于stack exchange,提问作者Leif Andersen
相关产品推荐
相关产品推荐

