参考章节《Rust 程序设计语言》第15.5章 RefCell<T> 和内部可变性模式
《Rust 程序设计语言》
上这一章,我实在是懒得吐槽
,举的例子太啰嗦(复杂),这一章不建议看这本书
关于这一章,我推荐看下面这本开源
书
参考章节《Rust语言圣经(Rust Course)》第3.4.5章 Cell 和 RefCell 内部可变性
内部可变性
好了我们先来引出问题
假如有一个外部库,它定义了一个消息发送器 Messenger
,它有一个 send
方法,用于发送一条消息
|
|
我们要在自己的代码中实现这个 trait
出于
性能
的考虑,消息先写到本地缓存(内存)
中,然后批量发送出去,因此在send
方法中,需要将消息先行插入到本地缓存msg_cache
中。
但是问题来了,该 send
方法的签名是 &self
因为发送消息
不需要修改自身
,因此原作者在定义时,使用了&self
的不可变借用,这个无可厚非
。
|
|
我们上面的代码不能通过编译,原因很简单,因为 &self 是不可变的
,我们总不能让库的作者去修改他的代码吧
此时就可以利用 RefCell<T>
了
|
|
由于本身我们的send()
方法是&self
不可变引用,因此正常情况下,我们不能改变其内部成员的值
,所以我们使用了RefCell<T>
RefCell<T>
允许我们对一个不可变的值
进行可变借用
,所以我们在send()
方法内部,获取了msg_cache
的可变借用
,从而使msg_cache
可以修改
但它对于外部代码
仍然是不可变的
|
|
这种只
在内部
对一个不可变的值
进行可变借用
,叫做内部可变性
,这是一种设计模式
,它和RefCell<T>
没有必然联系,但RefCell<T>
实现了内部可变性
什么叫做RefCell<T>
实现了内部可变性
? 请看如下代码
|
|
虽然 counter
是不可变的,不过我们仍然可以获取其内部值的可变引用
,所以说RefCell<T>
实现了内部可变性
RefCell
接下来我们来看看 RefCell
到底做了什么事情
首先,RefCell<T>
内部使用了不安全的代码
来模糊Rust的借用检查规则,这一点知道就行了,我们还没有学习到 unsafe
其次,RefCell<T>
将检查借用规则
步骤从编译时期转移到了运行时期
我们回忆一下借用规则
- 在任何给定时间里,要么只能拥有
一个可变引用
,要么只能拥有任意数量的不可变引用
- 引用必须
总是有效
的
在编译期检查借用规则,当不满足时出现错误
,而在运行时检查借用规则,如过不满足,则panic
下图展示了Box
、Rc
、RefCell
之间的区别,它出自杨旭的Rust视频
好了知道了这些后,我们来看看,上面的代码,为什么我们使用 RefCell<T>
后,程序就能编译通过了
首先在上面的定义中
msg_cache
本身是不可变
的(因为实例引用是&self
)这一点没问题吧
然后我们通过borrow_mut()
获取了一个msg_cache
可变借用,但根据借用规则
是不允许可变引用
和不可变引用
共存的
这种情况下正常是不允许编译
的(不允许你获取它的可变引用
),但RefCell<T>
将检查
步骤从编译时
转移到了运行时
,此时程序就能正常编译通过了
Rc和RefCell组合使用
下面的例子是原封不动照搬《Rust语言圣经》,因为我觉得这个例子举的很好理解,比《Rust 程序设计语言》中的例子好的多
在 Rust 中,一个常见的组合就是 Rc
和 RefCell
在一起使用
|
|
上面代码中,我们使用 RefCell<String>
包裹一个字符串,同时通过 Rc
创建了它的三个所有者:s
、s1
和s2
,并且通过其中一个所有者 s2
对字符串内容进行了修改。
由于 Rc
的所有者们共享同一个底层的数据
,因此当一个所有者修改了数据时,会导致全部所有者持有的数据都发生了变化。
运行结果
|
|
总结一下
内部可变性
是一种设计模式
RefCell<T>
允许我们对一个不可变的值
进行可变借用
RefCell<T>
将借用规则
检查从编译期
转移到了运行时
RefCell<T>
只是转移了借用规则
检查的时机
,但并不是不检查了,如果不满足要求则会触发panic
例如下面这样是不允许的,这将触发
panic
|
|