joc*_*ull 22 rust interior-mutability
您什么时候需要使用Cell或RefCell?似乎有许多其他类型选择适合代替这些,文档警告说使用RefCell是一种"最后的手段".
使用这些类型是" 代码味 "吗?任何人都可以展示一个例子,使用这些类型比使用其他类型更有意义,例如Rc甚至Box?
Vla*_*eev 30
询问何时Cell或RefCell应该使用它并不完全正确Box,Rc因为这些类型解决了不同的问题.实际上,往往不是RefCell被用来一起用Rc,以便提供与共享所有权可变性.所以,是的,用例Cell并且RefCell完全依赖于代码中的可变性要求.
内部和外部的可变性在官方的Rust书中,在关于可变性的指定章节中得到了很好的解释.外部可变性与所有权模型密切相关,并且大多数情况下,当我们说某些东西是可变的或不可变的时,我们的意思就是外部可变性.外部可变性的另一个名称是继承可变性,这可能更清楚地解释了这一概念:这种可变性由数据所有者定义,并继承到您可以从所有者处获得的所有内容.例如,如果结构类型的变量是可变的,那么变量中结构的所有字段也是如此:
struct Point { x: u32, y: u32 }
// the variable is mutable...
let mut p = Point { x: 10, y: 20 };
// ...and so are fields reachable through this variable
p.x = 11;
p.y = 22;
let q = Point { x: 10, y: 20 };
q.x = 33; // compilation error
Run Code Online (Sandbox Code Playgroud)
继承的可变性还定义了可以从值中获取的引用类型:
{
let px: &u32 = &p.x; // okay
}
{
let py: &mut u32 = &mut p.x; // okay, because p is mut
}
{
let qx: &u32 = &q.x; // okay
}
{
let qy: &mut u32 = &mut q.y; // compilation error since q is not mut
}
Run Code Online (Sandbox Code Playgroud)
然而,有时候,继承的可变性是不够的.规范示例是引用计数指针,Rc在Rust中调用.以下代码完全有效:
{
let x1: Rc<u32> = Rc::new(1);
let x2: Rc<u32> = x1.clone(); // create another reference to the same data
let x3: Rc<u32> = x2.clone(); // even another
} // here all references are destroyed and the memory they were pointing at is deallocated
Run Code Online (Sandbox Code Playgroud)
乍一看不清楚可变性是如何与此相关的,但是回想一下,引用计数指针是这样调用的,因为它们包含一个内部引用计数器,当引用被复制(clone()在Rust中)并被销毁时(被淘汰)范围Rust).因此,即使它存储在非变量内,Rc 也必须自行修改mut.
这是通过内部可变性实现的.标准库中有一些特殊的类型,其中最基本的类型是UnsafeCell允许一个人解决外部可变性的规则,并且即使它被存储(传递)在非mut变量中也会变异.
另一种说内容具有内部可变性的方法是,这个东西可以通过&-reference 修改- 也就是说,如果你有一个类型的值,&T你可以修改T它指向的状态,那么T就有内部可变性.
例如,Cell可以包含Copy数据,即使它存储在非mut位置,也可以进行变异:
let c: Cell<u32> = Cell::new(1);
c.set(2);
assert_eq!(c.get(), 2);
Run Code Online (Sandbox Code Playgroud)
RefCell可以包含非Copy数据,它可以&mut指向其包含的值,并在运行时检查是否缺少别名.这些都在他们的文档页面上详细解释.
事实证明,在绝大多数情况下,您只能轻松地使用外部可变性.Rust中大多数现有的高级代码都是这样编写的.然而,有时,内部可变性是不可避免的,或者使代码更清晰.Rc上面已经描述了一个示例实现.另一个是当你需要共享可变所有权时(也就是说,你需要从代码的不同部分访问和修改相同的值) - 这通常是通过实现Rc<RefCell<T>>,因为它不能单独使用引用来完成.另一个例子是Arc<Mutex<T>>,Mutex作为内部可变性的另一种类型,它也可以安全地跨线程使用.
所以,你可以看到,Cell而RefCell不是用于替换Rc或Box; 他们解决了在默认情况下不允许的地方为您提供可变性的任务.您可以编写代码而不使用它们; 如果你遇到需要它们的情况,你就会知道它.
Cells和RefCells不是代码气味; 将它们描述为"最后的手段"的唯一原因是它们将检查可变性和别名规则的任务从编译器移动到运行时代码,如下所示RefCell:你不能让两个&mut指向相同的数据同时,这是由编译器静态强制执行的,但是对于RefCells,您可以要求同样RefCell为您提供尽可能多的&muts - 除非您不止一次这样做,否则会对您造成恐慌,在运行时强制执行别名规则.恐慌可能比编译错误更糟糕,因为您只能在运行时而不是在编译时发现导致错误的错误.但是,有时候,编译器中的静态分析器限制性太强,你确实需要"解决"它.
小智 10
不,Cell而且RefCell不是"代码味道".通常,可变性是继承的,也就是说,当且仅当您对整个数据结构具有独占访问权时,您才可以改变字段或数据结构的一部分,因此您可以选择在该级别进行可变性mut(即,foo.x 继承)它的可变性或缺乏性foo).这是一个非常强大的模式,应该在它运行良好时使用(经常令人惊讶).但它并不能满足所有代码的需求.
Box并Rc没有什么与此有关.与几乎所有其他类型一样,它们尊重继承的可变性:Box如果您具有对该内容的独占,可变访问权限,则可以改变a的内容Box(因为这意味着您也可以独占访问内容).相反,你永远无法获得a &mut的内容,Rc因为它的性质Rc是共享的(即可以有多个Rcs引用相同的数据).
一种常见的情况Cell或者RefCell是,你需要几个地方之间共享的可变数据.&mut通常不允许对相同数据进行两次引用(并且有充分的理由!).但是,有时您需要它,并且单元格类型可以安全地执行它.
这可以通过共同的组合来完成Rc<RefCell<T>>,这允许数据在任何人使用它时保持不变并且允许每个人(但是一次只有一个!)来改变它.或者它可以很简单&Cell<i32>(即使单元格包含在更有意义的类型中).后者通常也用于内部,私有,可变状态,如引用计数.
文档实际上有几个你使用Cell或使用的例子RefCell.一个好的例子实际上Rc就是它自己 创建新的时Rc,必须增加引用计数,但引用计数在所有Rcs 之间共享,因此,通过继承的可变性,这可能无法工作.Rc实际上必须使用一个Cell.
一个好的指导方针是尝试编写尽可能多的代码而不使用单元格类型,但在没有它们的情况下使用它们时会使用它们太多.在某些情况下,有一个很好的解决方案,没有细胞,而且,有了经验,你将能够找到那些你以前错过它们,但总会有一些东西,没有它们是不可能的.
假设您想要或需要创建一些您选择的类型的对象并将其转储到Rc.
let x = Rc::new(5i32);
Run Code Online (Sandbox Code Playgroud)
现在,您可以轻松地创建另一个Rc指向完全相同的对象,从而指向内存位置:
let y = x.clone();
let yval: i32 = *y;
Run Code Online (Sandbox Code Playgroud)
由于在Rust中,您可能永远不会对存在任何其他引用的内存位置进行可变引用,Rc因此永远不能再次修改这些容器.
那么,如果您希望能够修改这些对象并使多个Rc指向同一个对象,该怎么办?
这是Cell和RefCell解决的问题.该解决方案称为"内部可变性",这意味着Rust的别名规则在运行时而不是编译时强制执行.
回到我们原来的例子:
let x = Rc::new(RefCell::new(5i32));
let y = x.clone();
Run Code Online (Sandbox Code Playgroud)
为了得到一个可变引用你的类型,您使用borrow_mut的RefCell.
let yval = x.borrow_mut();
*yval = 45;
Run Code Online (Sandbox Code Playgroud)
如果你已经将你的Rcs 值指向可变或非可变,那么该borrow_mut函数将会出现恐慌,从而强制执行Rust的别名规则.
Rc<RefCell<T>>只是一个例子RefCell,还有许多其他合法用途.但文档是对的.如果有另一种方法,请使用它,因为编译器无法帮助您推理RefCells.