我可以编写以下函数
f
并且代码可以按照我的预期进行编译:
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),
};
}
}
这里
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>`
我的猜测是,编译器无法确定我是否完成了
data
,因为它是 RefCell
,因此在运行时进行检查,但如果是这种情况,有什么方法可以“签入” ' 我借用的价值,因为它不再需要了?或者是我让它超出范围的唯一选择? (对于这个小例子来说,这很好,但对于较大的例子来说,在审美上令人不快。)
我的猜测是编译器无法确定我是否完成了data
编译器可以,但它选择不这样做,以避免更糟糕的意外。这里的具体规则是
Drop
代码的类型的值(例如 std::cell::Ref
)始终使该代码运行于 在作用域末尾,删除时间/顺序很重要,因为它可能会产生副作用,因此编译器会使其保持可预测性。
有什么方法可以“签入”我借用的价值,因为它不再需要了?
明确放弃它。像这样改变你的
match
,它将编译:
None => {
drop(data);
self.data.remove(&key);
}
现在编译器发现
data
不再存在,并且不再借用 self.data
,因此它允许您在之后专门借用 self.data
。