Vec内部可变性

时间:2016-05-02 10:59:47

标签: iterator rust mutable ownership borrowing

我有一个结构AppData,其中包含一个名为Vec<Box<Updatable>>的{​​{1}},其中包含使用以下函数实现特征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.data等的组合进行编译,但我不确定它是否是惯用的Rust。一些选择:

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

提前致谢!

2 个答案:

答案 0 :(得分: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 AppData传递给Updatable::update()update()的实施可以通过从self删除相应的项目来销毁objects! (如果AppData实际上没有提供一种方法来做这件事并不重要。)

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

为了确保update()Box调用期间保持活动状态,您需要将其包装在其他智能指针中。例如,您可以将其换成Rc而不是Rc。由于struct AppData { objects: Vec<Rc<RefCell<Updatable>>>, } 不允许您对其内容进行可变借用,因此您可能希望将其与RefCell结合使用,如下所示:

Vec

我们可以对整个struct AppData { objects: Rc<RefCell<Vec<Rc<RefCell<Updatable>>>>>, } 执行相同的操作:

objects

但是,这有一个限制:当您在App::update()中对objects进行迭代时,您将无法从Updatable::update()的实现中变异RefCell。如果你试图这样做,你的程序会感到恐慌,因为你不能在同一个objects上有多个活跃的可变借用。

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

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

答案 1 :(得分:2)

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

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

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