I have a struct AppData
with contains a Vec<Box<Updatable>>
called objects
, which contains structs that implement a trait Updatable
with the following function:
fn update(&mut self, data: &mut AppData) {
//default implementation accesses and mutates AppData, including the internal `objects` Vec and possibly also objects inside it
}
The AppData
struct is stored in a field data
in a struct App
with the following function:
pub fn update(&mut self) {
for d in self.data.objects.iter(){
d.update(&mut self.data);
}
}
I cannot do this beacuse Box<T>
is immutable. So I tried using indexers instead:
for i in 0..self.data.objects.len() {
let ref mut d = self.data.objects[i];
d.update(&mut self.data);
}
But then I get
cannot borrow
self.data
as mutable more than once at a time
So what do I do? I could probably get it to compile using combinations of RefCell
etc but I'm not sure it would be idiomatic Rust. A few alternatives:
Vec
and iterating over the clone instead. But I ran into trouble because Updateable
does not implement Sized
.RefCell
instead of Box
. I'm not sure I should need it since I'm not storing the references to the Vec
inside the Updatables
but that might not make a difference? I suppose RefCell
is supposed to be used over Rc
in this scenario because I want mutable references? Also this does not solve my problem because I still need to take ownership of self.data
somehow, right?self.data
after deconstructing self
and then placing it back into self after we are done with it. How do I do that?Thanks in advance!
You can use iter_mut()
instead of iter()
to get the same result as your indexer-based solution:
pub fn update(&mut self) {
for d in self.data.objects.iter_mut() {
d.update(&mut self.data);
}
}
(Yes, "same result" means we still get "cannot borrow self.data
as mutable more than once at a time".)
There are a couple of soundness issues in your program. First, by passing a &mut AppData
to Updatable::update()
, the implementation of update()
could destroy self
by removing the corresponding item from objects
! (It doesn't matter if AppData
doesn't actually provide a method to do that.)
Additionally, Updatable::update()
could invalidate the iterator in App::update()
by adding any item to or removing any item from objects
. Switching to an indexer-based loop only makes the problem worse, because your program might compile, but it will be buggy!
In order to ensure that your Updatable
remains alive for the duration of the update()
call, you need to wrap it in some other smart pointer. For example, you could wrap it in an 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
, like so:
struct AppData {
objects: Vec<Rc<RefCell<Updatable>>>,
}
We can do the same for the whole Vec
:
struct AppData {
objects: Rc<RefCell<Vec<Rc<RefCell<Updatable>>>>>,
}
However, that comes with a restriction: while you're iterating on objects
in App::update()
, you will not be able to mutate objects
from implementations of Updatable::update()
. If you try to do so, your program will panic, because you can't have more than one active mutable borrow on the same RefCell
.
If you need to be able to mutate objects
from implementations of Updatable::update()
, then you probably want App::update()
to iterate on whatever objects
contained when you start the loop. The simple solution to this is to clone
the Vec
just before the loop (we don't need Rc<RefCell<Vec<...>>>
for this).
However, cloning a Vec
every time (even when not necessary) might be expensive, so you may want to avoid doing so when it's not needed. Instead of cloning the Vec
systematically, we can wrap the Vec
in an Rc
(but no RefCell
this time!), then clone the Rc
before borrowing the vector in App::update()
. In AppData
, methods that want to mutate objects
would use Rc::make_mut
to clone the Vec
(if necessary!) and obtain a mutable reference. If the mutation occurs while App::update()
is active, this will clone the Vec
, which leaves the original Vec
alone so the iteration can continue. However, if there are no actives clones of the Rc
, then this will not do a clone, it'll just give you a mutable reference to the Vec
, because it's safe to do so.