当创建不可变和可变引用时,我们分别使用 & 和 &mut 语法。对于 RefCell 来说,则是 borrow 和
borrow_mut 方法,这属于 RefCell 安全 API 的一部分。borrow 方法返回 Ref 类型的智能指针,
borrow_mut 方法返回 RefMut 类型的智能指针。这两个类型都实现了 Deref,所以可以当作常规引用
对待。
RefCell 记录当前有多少个活动的 Ref 和 RefMut 智能指针。每次调用 borrow,RefCell
将活动的不可变借用计数加一。当 Ref 值离开作用域时,不可变借用计数减一。就像编译时借用规
则一样,RefCell 在任何时候只允许有多个不可变借用或一个可变借用。
如果我们尝试违反这些规则,相比引用时的编译时错误,RefCell 的实现会在运行时出现 panic。示
例 15-23 展示了对示例 15-22 中 send 实现的修改,这里我们故意尝试在相同作用域创建两个可变借用
以便演示 RefCell 不允许我们在运行时这么做:
文件名: src∕lib.rs
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &T, max: usize) -> LimitTracker {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send(“Error: You are over your quota!”);
} else if percentage_of_max >= 0.9 {
self.messenger
.send(“Urgent warning: You’ve used up over 90% of your quota!”);
} else if percentage_of_max >= 0.75 {
self.messenger
.send(“Warning: You’ve used up over 75% of your quota!”);
}
}
}
#[cfg(test)]
mod tests {
use super:😗;
use std::cell::RefCell;
struct MockMessenger {
sent_messages: RefCell<Vec>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();
one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}
示例 15-23:在同一作用域中创建两个可变引用并观察 RefCell panic
这里为 borrow_mut 返回的 RefMut 智能指针创建了 one_borrow 变量。接着用相同的方式在变量
two_borrow 创建了另一个可变借用。这会在相同作用域中创建两个可变引用,这是不允许的。当运行
库的测试时,示例 15-23 编译时不会有任何错误,不过测试会失败:
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
Finished test [unoptimized + debuginfo] target(s) in 0.91s
Running unittests (target/debug/deps/limit_tracker-e599811fa246dbde)
running 1 test
test tests::it_sends_an_over_75_percent_warning_message … FAILED
failures:
---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread ‘main’ panicked at ‘already borrowed: BorrowMutError’, src/lib.rs:60:53
note: run with RUST_BACKTRACE=1
environment variable to display a backtrace
failures:
tests::it_sends_an_over_75_percent_warning_message
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass ‘–lib’
注意代码 panic 和信息 already borrowed: BorrowMutError。这也就是 RefCell 如何在运行时处理
违反借用规则的情况。
在运行时捕获借用错误而不是编译时意味着将会在开发过程的后期才会发现错误,甚至有可能发布到
生产环境才发现;还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚。然而,使用
RefCell 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能。虽然有取
舍,但是我们可以选择使用 RefCell 来获得比常规引用所能提供的更多的功能。