问题描述:

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:

  • Cloning the Vec and iterating over the clone instead. But I ran into trouble because Updateable does not implement Sized.
  • Using 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?
  • Taking ownership of 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.

网友答案:

Box<T> is not intrinsically immutable. It plays by the same rules of inherited mutability as most other types. Your problem is a combination of different issues. First, .iter() gives an iterator over (immutable) references. So even if you didn't need to borrow self.data mutably while iterating over it, you'll get an error for that. If you want to iterate over mutable references, just do for d in &mut self.data.objects { ... } instead of the indexing dance.

Second, as you noticed, there is an issue with borrowing self.data while iterating over it. This is a potential issue in your design. For example, what should happen if update removes an object from the objects vector?

There is no easy one-size-fits-all solution to this. Maybe RefCell<Box<Trait>> will help, maybe it would be terrible design. Maybe update doesn't need the objects part of self.data and you can swap it out while iterating, which would prevent mutable aliasing. Maybe it's best to scrap the trait and pursue a wholly different design (it looks like you're trying to apply textbook OOP design which in my experience rarely works well in Rust).

相关阅读:
Top