0

我有一个包含被调用的结构AppDataVec<Box<Updatable>>其中objects包含实现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结构存储在具有以下功能data的结构中的字段中:App

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);
}

但后来我得到

一次不能多次借用self.datamutable

那我该怎么办?我可能可以使用RefCelletc 的组合来编译它,但我不确定它是否是惯用的 Rust。几种选择:

  • 克隆Vec并迭代克隆。但我遇到了麻烦,因为Updateable没有实现Sized.
  • 使用RefCell而不是Box. 我不确定我是否需要它,因为我没有存储对Vec内部的引用,Updatables但这可能没有什么不同?我想RefCell应该Rc在这种情况下使用,因为我想要可变引用?这也不能解决我的问题,因为我仍然需要以self.data某种方式拥有所有权,对吧?
  • self.data在解构后取得所有权,self然后在我们完成后将其放回自我。我怎么做?

提前致谢!

4

2 回答 2

2

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

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

(是的,“相同的结果”意味着我们仍然得到“不能self.data一次多次借用可变”。)

您的程序中有几个健全性问题。首先,通过传递一个&mut AppDatato Updatable::update(),可以通过从 !中删除相应的项目来update()销毁! (如果实际上没有提供这样做的方法并不重要。)selfobjectsAppData

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

为了确保您Updatable在通话期间保持活动状态update(),您需要将其包装在其他一些智能指针中。例如,您可以将其包装在 anRc而不是Box. 由于Rc不允许您对其内容进行可变借用,因此您可能希望将其与 结合起来RefCell,如下所示:

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

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

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

但是,这有一个限制:当您在 in 上进行迭代时objectsApp::update()您将无法objectsUpdatable::update(). 如果你尝试这样做,你的程序会恐慌,因为你不能在同一个RefCell.

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

但是,每次都克隆 a Vec(即使没有必要)可能会很昂贵,因此您可能希望在不需要时避免这样做。除了Vec系统地克隆之外,我们还可以将 in 包装起来VecRcRefCell这次不行!),然后Rc在借用向量之前克隆 the App::update()。在AppData中,想要变异的方法objects将用于Rc::make_mut克隆Vec(如有必要!)并获取可变引用。App::update()如果在激活时发生突变,这将克隆 ,这将Vec留下原始的Vec,以便迭代可以继续。但是,如果没有 的活动克隆Rc,那么这将不会进行克隆,它只会为您提供对Vec,因为这样做是安全的。

于 2016-05-02T11:35:40.120 回答
2

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

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

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

于 2016-05-02T11:35:53.707 回答