我有一个结构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
的所有权,然后在完成之后将其重新置于自我状态。我该怎么做?提前致谢!
答案 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
存在问题。 这是您设计中的潜在问题。例如,如果update
从objects
向量中移除对象,会发生什么?
对此没有简单的一刀切解决方案。也许RefCell<Box<Trait>>
会有所帮助,也许这将是一个糟糕的设计。也许update
不需要objects
的{{1}}部分,您可以在迭代时将其交换掉,这样可以防止可变别名。也许最好放弃这个特性并采用完全不同的设计(看起来你正在尝试应用教科书OOP设计,根据我的经验,在Rust中很少有效。)