1 - Rust标准库中的cell介绍
https://doc.rust-lang.org/std/cell/index.html
可共享的可变容器。
Rust的内存安全就是基于这个规则。给定一个对象T,只可能有以下情况之一。
- 有该对象的多个不可变的引用(&T)(也称为别名/aliasing)。
- 有该对象的一个可变的引用(&mut T)(也称为可突变性/mutability)。
这是由Rust编译器强制执行的。但是,在有些情况下,这个规则不够灵活。有时需要对一个对象有多个引用,但又要对其进行改变。
可共享的可变容器的存在是为了允许以可控的方式进行修改,甚至在存在别名的情况下也是如此。Cell
Cell
Cell类型有两种风格:Cell
- 对于实现Copy的类型,get方法可以检索当前的内部值。
- 对于实现Default的类型,take方法用Default::default()替换当前内部值,并返回被替换的值。
- 对于所有类型,replace方法替换了当前的内部值并返回被替换的值,而 into_inner方法则消费 Cell
并返回内部值。此外,set方法替换了内部值,丢弃了被替换的值。
RefCell
何时选择内部可变性
比较常见的继承式可突变性,即必须有唯一的访问权限才能改变一个值,这是Rust语言的关键元素之一,它使Rust能够对指针别名进行有力的推理,从静态上防止了崩溃bug。正因为如此,继承式的可变性是首选,而内部可变性是不得已而为之。由于cell类型可以在不允许改变的地方进行改变,因此在某些情况下,内部突变可能是合适的,甚至是必须使用的,例如:
- 在不可变的事物 “内部"引入可变性
- 逻辑上不可变方法的实现细节。
- Clone的可变实现。
在不变的事物内部引入可变性
许多共享的智能指针类型,包括Rc
那么,在共享指针类型里面放一个RefCell
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::rc::Rc;
fn main() {
let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
// Create a new block to limit the scope of the dynamic borrow
{
let mut map: RefMut<_> = shared_map.borrow_mut();
map.insert("africa", 92388);
map.insert("kyoto", 11837);
map.insert("piccadilly", 11826);
map.insert("marbles", 38);
}
// Note that if we had not let the previous borrow of the cache fall out
// of scope then the subsequent borrow would cause a dynamic thread panic.
// This is the major hazard of using `RefCell`.
let total: i32 = shared_map.borrow().values().sum();
println!("{}", total);
}
注意,这个例子使用的是Rc
逻辑上不可更改的方法的实施细节
偶尔,在API中不公开 “在引擎盖下 “发生的修改可能是可取的。这可能是因为在逻辑上,操作是不可更改的,但例如,缓存会迫使实现者执行突变;也可能是因为你必须使用可变来实现一个trait方法,而这个trait方法最初定义为取&self。
use std::cell::RefCell;
struct Graph {
edges: Vec<(i32, i32)>,
span_tree_cache: RefCell<Option<Vec<(i32, i32)>>>
}
impl Graph {
fn minimum_spanning_tree(&self) -> Vec<(i32, i32)> {
self.span_tree_cache.borrow_mut()
.get_or_insert_with(|| self.calc_span_tree())
.clone()
}
fn calc_span_tree(&self) -> Vec<(i32, i32)> {
// Expensive computation goes here
vec![]
}
}
Clone的可变实现
这只是前者的一个特殊但是很常见的案例:隐藏看起来是不可改的操作中的可变性。克隆方法预计不会改变源值,并且声明取&self,而不是&mut self。因此,克隆方法中发生的任何改变都必须使用cell类型。例如,Rc