内部可变性(interior mutability
)是RUST
的设计模式之一,它允许你在只持有不可变引用的前提下对数据进行修改。为了能改变数据,内部可变性模式在它的数据结构中使用了unsafe
(不安全)代码来绕过RUST
正常的可变性和借用规则。
假如我们能够保证自己的代码在运行时符合借用规则,那么即使编译器无法在编译阶段保证符合借用规则,也能使用那些采用了内部可变性模式的类型。
对于一般引用和Box<T>
的代码,RUST
会在编译阶段强制代码遵守借用规则。而对于RefCell<T>
的代码,RUST
则只会在运行时检查这些规则,并在违反借用规则的情况下触发panic
来终止程序。
与Rc<T>
相似,RefCell<T>
只能被用于单线程场景中。强行将它用于多线程环境中会产生编译时错误。下面是选择使用Box<T>
、Rc<T>
和RefCell<T>
的依据:
Rc<T>
允许一份数据有多个所有者,而Box<T>
和RefCell<T>
都只有一个所有者Box<T>
允许在编译时检查可变或不可变借用,Rc<T>
仅允许编译时检查不可变借用,RefCell<T>
允许运行时检查可变或不可变借用。- 由于
RefCell<T>
允许我们在运行时检查可变借用,所以即使RefCell<T>
本身是不可变的,我们仍然能够更改其中存储的值。
rust
pub trait Messager {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: 'a + Messager> {
messager: &'a T,
value: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messager,
{
pub fn new(messager: &T, value: usize) -> LimitTracker<T> {
LimitTracker {
messager: messager,
value: value,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
self.messager.send("msg");
}
}
这段代码的重点是Messager trait
,它唯一的send
方法可以接受self
的不可变引用及一条文本消息作为参数。
示例的结构体同时使用生命周期标注和泛型约束,结构体实现关联函数new
和方法set_value
。紧跟impl
后的尖括号声明指明结构体自身的约束。
下面对set_value
做单元测试,mock
一个实现了Message trait
的结构体MockMessage
进行验证,调用send
方法会向结构体字段中写入值。
rust
#[cfg(test)]
mod test {
use super::*;
struct MockMessage {
sent_message: Vec<String>,
}
impl MockMessage {
fn new() -> MockMessage {
MockMessage {
sent_message: vec![],
}
}
}
impl Messager for MockMessage {
fn send(&self, msg: &str) {
self.sent_message.push(String::from(msg));
}
}
#[test]
fn send_warning_message() {
let mock_messager = MockMessage::new();
let mut limit_tracker = LimitTracker::new(&mock_messager, 2);
limit_tracker.set_value(10);
}
}
编译器报错,send
方法接收的self
属于不可变引用,我们无法修改MockMessage
中的内容。如果将send
方法self
修改为&mut self
又不符合Messager trait
约束。
在保持外部值不可变的前提下,使用RefCell<T>
来修改内部存储的值。sent_message
字段类型调整成RefCell<Vec<String>>
,最后调整成下面这个样子:
rust
#[cfg(test)]
mod test {
use super::*;
use std::cell::RefCell;
struct MockMessage {
sent_message: RefCell<Vec<String>>,
}
impl MockMessage {
fn new() -> MockMessage {
MockMessage {
sent_message: RefCell::new(vec![]),
}
}
}
impl Messager for MockMessage {
fn send(&self, msg: &str) {
self.sent_message.borrow_mut().push(String::from(msg));
}
}
#[test]
fn send_warning_message() {
let mock_messager = MockMessage::new();
let mut limit_tracker = LimitTracker::new(&mock_messager, 2);
limit_tracker.set_value(10);
}
}
我们调用RefCell<Vec<String>>
类型的borrow_mut
方法来获取内部值的可变引用,接着,我们便可以在动态数据的可变引用上调用push
方法来存入数据。
我们会在创建不可变引用和可变引用时分别使用语法&
和&mut
。RefCell<T>
实现了borrow
和borrow_mut
方法分别返回Ref<T>
与RefMut<T>
这两种智能指针。由于这两种指针都实现了Deref
,所以我们可以把它们当做一般的引用来对待。
RefCell<T>
会记录当前存在多少个活跃的Ref<T>
和RefMut<T>
智能指针,在任何一个给定的时间里,它只允许你拥有多个不可变借用和一个可变借用。当我们违背借用规则时,相比于一般引用导致的编译错误,RefCell<T>
会在运行时触发panic
。