具有内部可变性的 Vec

Vec with interior mutability

我有一个结构 AppData,其中包含一个名为 objectsVec<Box<Updatable>>,它包含实现具有以下功能的特征 Updatable 的结构:

fn update(&mut self, data: &mut AppData) {  
    //default implementation accesses and mutates AppData, including the internal `objects` Vec and possibly also objects inside it
}

AppData 结构存储在结构 App 的字段 data 中,具有以下功能:

pub fn update(&mut self) {

    for d in self.data.objects.iter(){
        d.update(&mut self.data);
    }

}

我不能这样做,因为 Box<T> 是不可变的。所以我尝试改用索引器:

for i in 0..self.data.objects.len() {
    let ref mut d = self.data.objects[i];
    d.update(&mut self.data);
}

但后来我得到

cannot borrow self.data as mutable more than once at a time

那我该怎么办?我可能可以使用 RefCell 等的组合来编译它,但我不确定它是否是惯用的 Rust。几个备选方案:

提前致谢!

您可以使用 iter_mut() 而不是 iter() 以获得与基于索引器的解决方案相同的结果:

pub fn update(&mut self) {
    for d in self.data.objects.iter_mut() {
        d.update(&mut self.data);
    }
}

(是的,"same result" 意味着我们仍然得到 "cannot borrow self.data as mutable more than once at a time"。)

您的程序中存在一些健全性问题。首先,通过将 &mut AppData 传递给 Updatable::update()update() 的实现可以通过从 objects 中删除相应的项目来破坏 self! (如果 AppData 实际上没有提供这样做的方法也没关系。)

此外,Updatable::update() 可以通过向 objects 添加或删除任何项目来使 App::update() 中的迭代器无效。切换到基于索引器的循环只会使问题变得更糟,因为您的程序可能会编译,但它会出现错误!

为了确保您的 Updatableupdate() 调用期间保持活动状态,您需要将其包装在其他一些智能指针中。例如,您可以将其包装在 Rc instead of a Box. As Rc doesn't let you take a mutable borrow to its contents, you may want to combine this with RefCell 中,如下所示:

struct AppData {
    objects: Vec<Rc<RefCell<Updatable>>>,
}

我们可以对整个做同样的事情Vec:

struct AppData {
    objects: Rc<RefCell<Vec<Rc<RefCell<Updatable>>>>>,
}

但是,这有一个限制:当您在 App::update() 中迭代 objects 时,您将无法从 [=17= 的实现中改变 objects ].如果您尝试这样做,您的程序将会崩溃,因为您不能在同一个 RefCell.

上拥有多个活动的可变借用。

如果您需要能够从 Updatable::update() 的实现中改变 objects,那么您可能希望 App::update() 在启动时迭代包含的任何 objects环形。对此的简单解决方案是在循环之前 clone Vec(我们不需要 Rc<RefCell<Vec<...>>>)。

但是,每次(即使没有必要)克隆 Vec 可能代价高昂,因此您可能希望在不需要时避免这样做。我们可以将 Vec 包装在 Rc 中,而不是系统地克隆 Vec(但这次没有 RefCell!),然后在借用之前克隆 Rc App::update() 中的向量。在 AppData 中,想要改变 objects 的方法将使用 Rc::make_mut 克隆 Vec(如果需要!)并获得可变引用。如果在 App::update() 处于活动状态时发生突变,这将克隆 Vec,从而使原始 Vec 单独存在,以便迭代可以继续。但是,如果没有 Rc 的活动克隆,那么这不会进行克隆,它只会为您提供对 Vec 的可变引用,因为这样做是安全的。

Box<T> 本质上是 而不是 不可变的。它遵循与大多数其他类型相同的继承可变性规则。您的问题是不同问题的组合。首先,.iter() 给出了(不可变)引用的迭代器。因此,即使您在遍历它时不需要可变地借用 self.data ,您也会因此得到一个错误。如果您想遍历可变引用,只需执行 for d in &mut self.data.objects { ... } 而不是索引舞蹈。

其次,正如您所注意到的,在迭代时借用 self.data 存在问题。 这是您设计中的一个潜在问题。例如,如果 updateobjects 向量中删除一个对象会发生什么?

对此没有简单的一刀切的解决方案。也许 RefCell<Box<Trait>> 会有所帮助,也许这会是糟糕的设计。也许 update 不需要 self.dataobjects 部分,您可以在迭代时将其换掉,这将防止可变别名。也许最好放弃这个特性并追求完全不同的设计(看起来你正在尝试应用教科书 OOP 设计,根据我的经验,这在 Rust 中很少有效)。