0

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();
}
  • Please provide a [mre] and add the errors you're getting to the question. In particular you say that your code gives you _two_ errors, but your playground link gives _four_ errors. – Jmb Nov 02 '22 at 16:23
  • Here you go: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5c70b33070e8bd632c0fcd72deea3a7d. Don't be afraid to give things static lifetimes. When I was first learning Rust I was hesitant to use `'static`, but it just means something does not have lifetime constraints. As such, all primitive types are `'static`. Also, `as_any` needs to return a mutable reference and you can't get extra mutable references into zoo at the same time. – Locke Nov 02 '22 at 16:29
  • Yes sorry for the 4 errors in the playground, meanwhile I added the 'static lifetime (and understood its meaning). – Antoine Morel Nov 02 '22 at 16:54

2 Answers2

1

Using

    fn host<A: Animal + 'static>(&mut self, a: A) -> &mut (dyn Animal + 'static) {
        self.animals.push(Box::new(a));
        let pushed_box = self.animals.last_mut().expect("error");
        pushed_box.as_mut()
    }

will fix the first error. You're basically telling the compiler that A will own all the data or only have static references. You can't return an A because self.animals doesn't consist of only A which at this point is a concrete type.

The second error arises because you keep the reference to dog around till after you edited zoo again. At that point the whole animals Vec could have moved somewhere else and thus dog is no longer valid.

cafce25
  • 15,907
  • 4
  • 25
  • 31
  • Thanks for the reply. But if I do that, I won't be able to call methods of Dog or Cat that are not required by the Animal trait, that's why I wanted to downcast to the actual type. For the second error, I understand it, but it doesn't give me a hint of how to get the expected behavior. I understand the animals Vec could be modified and the references not be valid anymore, but that's not my intention. How could I tell rust that once the collection is filled, it won't evolve anymore, and having references to its items must be valid ? – Antoine Morel Nov 02 '22 at 17:09
  • There is really only 2 options I can think of, 1. do what you want to do with dog before you add another animal to the zoo. 2. Take the reference after you've added all the animals. Of course at that point you'd have to downcast in main or only rely on methods in `Animal` – cafce25 Nov 02 '22 at 17:39
1

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();
}
fred xia
  • 151
  • 3
  • That was indeed exactly what I needed, thank you. I actually didn't go far enough reading the rust book, I should have been more patient before playing too much... but it gave me the opportunity to take a bigger dive into Rc and RefCell. Btw, no need to get a mutable reference to the last pushed item to clone it, the following code is enough : `Rc::clone(self.animals.last().expect("error"))` – Antoine Morel Nov 06 '22 at 17:46