Search code examples
rustcollectionstrait-objectsmutable-reference

Get mutable reference to an item in trait objects collection


To play around with Rust, I'm trying to get the following code working (playground, please don't mind the commented blocks, they are for further investigations).

Basically I would like to store in a collection several items of several types implementing a common trait. I can't use an enum because I want the user to implement the trait for more types, so I use trait objects.

I want, after its insertion in the collection, to get a mutable reference to the item, so that the user can use it independently from the fact that it's stored in a collection.

The following code gives me two errors that I have no idea how to solve.

use std::any::Any;

// Trait needed to represent an Animal, with as_any() method to support downcasting
trait Animal {
    fn as_any(&self) -> &dyn Any;
    fn feed(&mut self);
    fn is_fed(&self) -> bool;
}

// Realization of Animal for a dog
struct Dog {
    is_fed: bool
}
impl Animal for Dog {
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn feed(&mut self) {
        self.is_fed = true;
    }
    fn is_fed(&self) -> bool {
        self.is_fed
    }
}

// Realization of Animal for a cat
struct Cat {
    is_fed: bool
}
impl Animal for Cat {
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn feed(&mut self) {
        self.is_fed = true;
    }
    fn is_fed(&self) -> bool {
        self.is_fed
    }
}

// Struct to host a list of trait objects implementing the trait Animal
struct Zoo {
    animals: Vec<Box<dyn Animal>>
}
impl Zoo {
    fn new() -> Zoo {
        Zoo{animals: Vec::new()}
    }

    // Method to append a new animal to the zoo and get a mutable reference to the newly created object
    fn host<'a, A: Animal>(&mut self, a: A) -> &mut A {
        self.animals.push(Box::new(a));
        let pushed_box = self.animals.last_mut().expect("error");
        pushed_box.as_any().downcast_mut::<A>().expect("error") // error: cannot borrow data in a `&` reference as mutable
    }
}

fn main()
{
    let mut zoo = Zoo::new();
              zoo.host(Dog{is_fed:false});
    let cat = zoo.host(Cat{is_fed:false});
              zoo.host(Cat{is_fed:false});
              zoo.host(Dog{is_fed:false});
              zoo.host(Cat{is_fed:false});
    let dog = zoo.host(Dog{is_fed:false});
              zoo.host(Cat{is_fed:false}); // error : cannot borrow `zoo` as mutable more than once at a time
    dog.feed();
}

Solution

  • You can use Rc<RefCell<dyn Animal>> for the collection.

    struct Zoo {
        animals: Vec<std::rc::Rc<RefCell<dyn Animal>>>,
    }
    impl Zoo {
        fn new() -> Zoo {
            Zoo{animals: Vec::new()}
        }
        fn host<A: Animal + 'static>(&mut self, a: A) -> std::rc::Rc<RefCell<dyn Animal>> {
            self.animals.push(std::rc::Rc::new(RefCell::new(a)));
            let pushed_box = self.animals.last_mut().expect("error");
            pushed_box.clone()
        }
    }
    

    and use borrow_mut() to get mutable reference to an animal. Or try_borrow_mut() to make sure that the code won't panic if another borrow exists for the same animal.

    fn main()
    {
        let mut zoo = Zoo::new();
                  zoo.host(Dog{is_fed:false});
        let cat = zoo.host(Cat{is_fed:false});
                  zoo.host(Cat{is_fed:false});
                  zoo.host(Dog{is_fed:false});
                  zoo.host(Cat{is_fed:false});
        let dog = zoo.host(Dog{is_fed:false});
                  zoo.host(Cat{is_fed:false});
        dog.borrow_mut().feed();
    }