Convert generic trait T back to struct


#1

Hello guys!

I’m new to Rust, but read many articles and the entire book, even so I’m stuck with a problem and I’m willing to get help on how to solve this. Here is the code to reproduce the problem:

use std::fmt::Debug;
use std::rc::Rc;
use std::cell::RefCell;
use std::cell::Ref;

trait Behaviour {
    fn name(&self) -> &'static str;
    fn doBehaviour(&self);
}

struct SomeBehaviour;

impl Behaviour for SomeBehaviour {
    fn name(&self) -> &'static str {
        "Some Behaviour"
    }

    fn doBehaviour(&self) {
        println!("Doing something...");
    }
}

struct Container<'a> {
    behaviours: Vec<Rc<RefCell<Behaviour + 'a>>>,
}

impl<'a> Container<'a> {
    fn new<T>(main_behaviour: T) -> Container<'a>
    where
        T: Behaviour + 'a,
    {
        Container { behaviours: vec![Rc::new(RefCell::new(main_behaviour))] }
    }

    fn add<T>(&mut self, behaviour: T)
    where
        T: Behaviour + 'a,
    {
        self.behaviours.push(Rc::new(RefCell::new(behaviour)));
    }

    fn get<T>(&mut self, name: &str) -> Option<&mut T> {
        if let Some(b) = self.behaviours.iter().find(|b| *b.borrow().name() == *name) {
            return Some(&mut *b.borrow());
        }
        None
    }
}

fn main() {
    let mut container = Container::new(SomeBehaviour);
    let someBehaviour = container.get::<SomeBehaviour>("Some Behaviour").unwrap();
    println!("Got behaviour: {}", someBehaviour.name());
}

On playground

I want to have a container object which holds all behaviour objects inside it. Those object behaviours implements a trait, which I use as a generic type on behaviours vector inside container struct.

I keep the ownership with the container and give to requesters only a mutable reference, so the behaviour objects will live as long the container exists.

The problem is I don’t know how to convert back from trait to generic T to return the function (get). If you attempt to run the bellow code you’ll see an error saying missmatch types (T and Behaviour trait)

error[E0308]: mismatched types
  --> <anon>:44:25
   |
44 |             return Some(&mut *b.borrow());
   |                         ^^^^^^^^^^^^^^^^ expected type parameter, found trait Behaviour
   |
   = note: expected type `&mut T`
              found type `&mut Behaviour + 'a`

How can I accomplish this design? On C++ I could use some reinterpret_cast and it would work.

Thanks for your attention!


#2

Here is a Stackoverflow question dealing with a similar issue: https://stackoverflow.com/questions/33687447/how-to-get-a-struct-reference-from-a-boxed-trait
It seems that this is really really unsafe to do. You can’t just reinterpret_cast (which is similar to std::mem::transmute in rust) a trait object in rust because it isn’t just a pointer. If you try it you get a sizing error from the compiler. You would need to know the memory layout of the trait object to access the data pointer and then transmute the data pointer.

Why do you need to convert back to T? Given that you actually have to know T you could store it directly or use Any to store objects of different structs. I came up with something like the following, though I don’t know if it satisfies all use cases.
Anyhow I would rather think about the design again, e.g., if the “names” are a finite set then consider using an enum instead.

trait Behaviour {
    fn name(&self) -> &'static str;
    fn doBehaviour(&self);
}

struct SomeBehaviour;

impl Behaviour for SomeBehaviour {
    fn name(&self) -> &'static str {
        "Some Behaviour"
    }

    fn doBehaviour(&self) {
        println!("Doing something...");
    }
}

struct Container {
    behaviours: Vec<Box<std::any::Any>>,
}

impl Container {
    fn new<T>(main_behaviour: T) -> Container
    where
        T: Behaviour + std::any::Any,
    {
        Container { behaviours: vec![Box::new(main_behaviour)] }
    }

    fn add<T>(&mut self, behaviour: T)
    where
        T: Behaviour + std::any::Any,
    {
        self.behaviours.push(Box::new(behaviour));
    }

    fn get<T: Behaviour + std::any::Any>(&mut self, name: &str) -> Option<& T> {
        if let Some(b) = self.behaviours.iter().find(|b| b.downcast_ref::<T>().map(|x| x.name()) == Some(name)) {
            return b.downcast_ref::<T>();
        }
        None
    }
}

fn main() {
    let mut container = Container::new(SomeBehaviour);
    let someBehaviour = container.get::<SomeBehaviour>("Some Behaviour").unwrap();
    println!("Got behaviour: {}", someBehaviour.name());
}


#3

Thanks! That did the trick.