Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to cast a trait object to another trait object? [duplicate]

I tried the following code:

trait TraitA {
    fn say_hello(&self) {
        self.say_hello_from_a();
    }
    fn say_hello_from_a(&self);
}

trait TraitB {
    fn say_hello(&self) {
        self.say_hello_from_b();
    }
    fn say_hello_from_b(&self);
}

struct MyType {}

impl TraitA for MyType {
    fn say_hello_from_a(&self) {
        println!("Hello from A");
    }
}

impl TraitB for MyType {
    fn say_hello_from_b(&self) {
        println!("Hello from B");
    }
}

fn main() {
    let a: Box<dyn TraitA> = Box::new(MyType {});
    let b: Box<dyn TraitB>;

    a.say_hello();
    b = a;
    b.say_hello();
}

I get the following compilation error:

error[E0308]: mismatched types
  --> src/main.rs:34:9
   |
34 |     b = a;
   |         ^ expected trait `TraitB`, found trait `TraitA`
   |
   = note: expected struct `std::boxed::Box<dyn TraitB>`
              found struct `std::boxed::Box<dyn TraitA>`

I declared two traits and a type called MyType and implemented both traits for MyType. I created a new trait object TraitA of type MyType which I called a. Since a also implements TraitB, I thought it should be able to be casted as TraitB.

I haven't figured out if it's even possible. If it is, how can I cast trait object a into TraitB?

In C++, I would use something similar to std::dynamic_pointer_cast<TraitB>(a); for the same purpose.

Here's an example of a case where I could use lateral casting: I have a struct with some data inside that represents some real life entity:

struct MyType {
    a: i32,
    b: i32,
}

Instances of this type can be used in at least two different parts of the code base. On both parts I need a behavior called get_final_value.

The interesting part is that get_final_value should respond differently depending on who called it.

  • Why don't I split the type into two different ones?: Technically, by design, a and b belong together, not to say that get_final_value() uses both values to compute the result.

  • Why not use generics/static dispatch? Because MyType is just one example. In the real case I have different structs, all of them implementing both traits in different ways.

  • Why not use Any trait? To be honest, I didn't know of it's existence until recently. I don't recall The Rust Programming Language mentioning it. Anyway, it seems you need to know the concrete type to do a cast from Any to that concrete type and then to the trait object.

like image 886
dospro Avatar asked Apr 29 '19 00:04

dospro


1 Answers

Another option is to create a trait that uses both TraitA and TraitB as supertraits and provides a cast to each type:

trait TraitC: TraitA + TraitB {
    fn as_trait_a(&self) -> &dyn TraitA;
    fn as_trait_b(&self) -> &dyn TraitB;
}

Then have MyType implement it:

impl TraitC for MyType {
    fn as_trait_a(&self) -> &dyn TraitA {
        self
    }
    fn as_trait_b(&self) -> &dyn TraitB {
        self
    }
}

Once you do that, you can use TraitC for your Box and your program logic that uses both TraitA and TraitB together.

Sample main to show various ways to use:

fn test_a(a: &TraitA) {
    a.say_hello();
}
fn test_b(b: &TraitB) {
    b.say_hello();
}

fn main() {
    let c: Box<dyn TraitC> = Box::new(MyType {});

    TraitA::say_hello(&*c);
    TraitB::say_hello(&*c);

    c.as_trait_a().say_hello();
    c.as_trait_b().say_hello();

    test_a(c.as_trait_a());
    test_b(c.as_trait_b());

    let a: &dyn TraitA = c.as_trait_a();
    a.say_hello();
    let b: &dyn TraitB = c.as_trait_b();
    b.say_hello();
}

Rust Playground

If A and B do truly belong together, this better represents that and still gives you the freedom to use them separately if you desire.

like image 139
Jere Avatar answered May 24 '23 07:05

Jere