Rust 一种具有内部变异性的细胞,允许任意的变异行为
标准结构提供了内部可变性,但只允许一些突变方法,如set()、swap()和replace()。所有这些方法都会改变单元格的全部内容。 但是,有时需要更具体的操作,例如,仅更改单元格中包含的部分数据 所以我尝试实现某种通用单元,允许任意数据操作。 操作由用户定义的闭包表示,该闭包接受对单元格内部数据的单个参数&mut引用,因此用户自己可以决定如何处理单元格内部。下面的代码演示了这个想法:Rust 一种具有内部变异性的细胞,允许任意的变异行为,rust,interior-mutability,Rust,Interior Mutability,标准结构提供了内部可变性,但只允许一些突变方法,如set()、swap()和replace()。所有这些方法都会改变单元格的全部内容。 但是,有时需要更具体的操作,例如,仅更改单元格中包含的部分数据 所以我尝试实现某种通用单元,允许任意数据操作。 操作由用户定义的闭包表示,该闭包接受对单元格内部数据的单个参数&mut引用,因此用户自己可以决定如何处理单元格内部。下面的代码演示了这个想法: use std::cell::UnsafeCell; struct MtCell<Data>{
use std::cell::UnsafeCell;
struct MtCell<Data>{
dcell: UnsafeCell<Data>,
}
impl<Data> MtCell<Data>{
fn new(d: Data) -> MtCell<Data> {
return MtCell{dcell: UnsafeCell::new(d)};
}
fn exec<F, RetType>(&self, func: F) -> RetType where
RetType: Copy,
F: Fn(&mut Data) -> RetType
{
let p = self.dcell.get();
let pd: &mut Data;
unsafe{ pd = &mut *p; }
return func(pd);
}
}
// test:
type MyCell = MtCell<usize>;
fn main(){
let c: MyCell = MyCell::new(5);
println!("initial state: {}", c.exec(|pd| {return *pd;}));
println!("state changed to {}", c.exec(|pd| {
*pd += 10; // modify the interior "in place"
return *pd;
}));
}
使用std::cell::UnsafeCell;
结构MtCell{
dcell:不完美,
}
植入MtCell{
fn新(d:数据)->MtCell{
返回MtCell{dcell:UnsafeCell::new(d)};
}
fn exec(&self,func:F)->RetType其中
RetType:复制,
F:Fn(&mut数据)->RetType
{
设p=self.dcell.get();
设pd:&mut数据;
不安全{pd=&mut*p;}
返回函数(pd);
}
}
//测试:
类型MyCell=MtCell;
fn main(){
设c:MyCell=MyCell::new(5);
println!(“初始状态:{}”,c.exec(|pd{return*pd;}));
println!(“州改为{}),c.exec(| pd|{
*pd+=10;//修改内部“就位”
返回*pd;
}));
}
但是,我对代码有一些担心
[编辑]我希望无运行时故障的零成本抽象,因此RefCell不是完美的解决方案。您的代码不安全,因为您可以在
c.exec
内部调用c.exec
,以获得对单元格内容的两个可变引用,如仅包含安全代码的代码片段所示:
让c:MyCell=MyCell::new(5);
c、 执行官{
//需要'RefCell'从'Fn'闭包内访问可变引用
设n=RefCell::new(n);
c、 执行官{
设n=&mut*n.借用;
//现在'n'和'm'是对相同数据的可变引用,尽管使用了
//没有不安全的代码。这很糟糕!
})
})
事实上,这正是我们同时拥有Cell
和RefCell
的原因:
只允许您获取和设置一个值,不允许您从不可变引用获取可变引用(从而避免上述问题),但它没有任何运行时成本Cell
允许您从不可变引用获取可变引用,但需要在运行时执行检查以确保安全RefCell
fn main(){
设mt_cell=MtCell::new(123i8);
mt|U cell.exec(参考1:&mut i8){
mt|U cell.exec(参考2:&mut i8){
println!(“双可变ref!:{:?}{:?},ref1,ref2);
})
})
}
您完全正确,引用不能在闭包外使用,但在闭包内,所有赌注都是无效的!事实上,在闭包中几乎所有对单元格的操作(读或写)都是未定义的行为(UB),可能会导致程序中的任何地方发生损坏/崩溃
Cell
通常不是最好的技术,但是如果不了解问题的更多信息,就不可能知道最佳解决方案是什么
如果您坚持使用单元格
,有一些安全的方法可以做到这一点。不稳定的(即beta)Cell::update()方法实际上是通过以下代码实现的(当T:Copy
时):
发布fn更新(&self,f:f)->T
哪里
F:fn一次(T)->T,
{
让old=self.get();
设new=f(old);
self.set(新);
新的
}
或者您可以使用Cell::get_mut()
,但我想这会破坏Cell
的全部功能
但是,通常只更改单元格的一部分的最佳方法是将其拆分为单独的单元格。例如,使用(单元格,单元格,单元格)
代替单元格
然而,在国际海事组织,Cell
很少是最好的解决方案。内部可变性在C和许多其他语言中都是一种常见的设计,但在Rust中则更为罕见,至少通过共享引用和单元格
,原因有很多(例如,它不是同步
,一般来说,没有&mut
,人们就不会期望内部可变性)。问问自己为什么要使用单元格
,如果真的不可能将代码重新组织为使用正常的&mut
引用
在我看来,底线实际上是关于安全性的:如果不管你做什么,编译器都会抱怨,似乎你需要使用不安全的,