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();
}
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();
}