Rust 一种具有内部变异性的细胞,允许任意的变异行为

Rust 一种具有内部变异性的细胞,允许任意的变异行为,rust,interior-mutability,Rust,Interior Mutability,标准结构提供了内部可变性,但只允许一些突变方法,如set()、swap()和replace()。所有这些方法都会改变单元格的全部内容。 但是,有时需要更具体的操作,例如,仅更改单元格中包含的部分数据 所以我尝试实现某种通用单元,允许任意数据操作。 操作由用户定义的闭包表示,该闭包接受对单元格内部数据的单个参数&mut引用,因此用户自己可以决定如何处理单元格内部。下面的代码演示了这个想法: use std::cell::UnsafeCell; struct MtCell<Data>{

标准结构提供了内部可变性,但只允许一些突变方法,如set()、swap()和replace()。所有这些方法都会改变单元格的全部内容。 但是,有时需要更具体的操作,例如,仅更改单元格中包含的部分数据

所以我尝试实现某种通用单元,允许任意数据操作。 操作由用户定义的闭包表示,该闭包接受对单元格内部数据的单个参数&mut引用,因此用户自己可以决定如何处理单元格内部。下面的代码演示了这个想法:

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
      允许您从不可变引用获取可变引用,但需要在运行时执行检查以确保安全
    据我所知,没有任何安全的方法可以解决这个问题,因此您需要在代码中做出选择,要么不需要运行时成本,但灵活性较低;要么更灵活,但运行时成本较低。

    这是Rust初学者常见的陷阱

  • 它是否安全,即某些安全但恶意的闭包是否可以通过使用此“通用”单元来打破生锈易变性/借用/寿命规则?我认为它是安全的,因为内部引用参数的生存期禁止其超出闭包调用时间的说明。但我仍然有疑问(我是新手)
  • 一句话,不

    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
    引用

    在我看来,底线实际上是关于安全性的:如果不管你做什么,编译器都会抱怨,似乎你需要使用
    不安全的