10

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
dospro
  • 450
  • 1
  • 5
  • 17
  • 1
    Would you really use a dynamic cast in C++ for casting _laterally_? I can see that you can use it to downcast to a concrete type, but I have never encountered a use case for a lateral cast. For what it's worth, [downcasting can be achieved using the `Any` trait](https://doc.rust-lang.org/std/index.html?search=downcast). – Sven Marnach Apr 29 '19 at 09:19
  • Could you elaborate more on how you're using these values and how you want to use them with `get_final_value`? One option for instance would before `get_final_value` to accept anything that implements some third trait, and then both `TraitA` and `TraitB` could expose a function to convert them into that third type. – loganfsmyth Apr 30 '19 at 02:42
  • On the point about `Any` - this uses code which lives inside Any to perform downcasting. Rust still doesn't natively support downcasting, that is a separate thing. Whoever has written Any has essentially written some Rust code to emulate downcasting, since it isn't supported. Also, Rust supports upcasting from a Type to a Trait Object. (Normal pointer to Fat Pointer.) I can't say about any other forms of upcasting, such as between Trait objects, because I haven't yet looked at that in detail. – FreelanceConsultant Jun 04 '23 at 18:54

2 Answers2

6

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jere
  • 3,124
  • 7
  • 15
  • This doesn't always work apparently, for example `Arc::downcast(self)` requires `T` to be `dyn Any + Send + Sync` and does not allow trait objects from supertraits. At least I don't know how. – stimulate Oct 11 '20 at 17:12
  • This is very strange because inheritance hierarchies usually get *wider* as they go down, not narrower. That suggests to me that using this pattern will lead to all sorts of diamond inheritance problems, although I can't say for sure yet. It "feels" wrong to me – FreelanceConsultant Jun 04 '23 at 18:57
2

Using Box<MyType> instead of Box<dyn Trait> solves this problem.

fn main() {
    let a = Box::new(MyType {});

    TraitA::say_hello(&*a);
    TraitB::say_hello(&*a);
}

In this case there's no need to use trait objects. Rust has a different paradigm from C++. In most cases you can usually use generic types to solve problems. If your problem is really suitable to solve with trait objects, you can refer to the OOP chapter in the book.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Peng Guanwen
  • 547
  • 3
  • 9
  • I know I can use MyType directly. My question goes more towards the case where you already have a trait object and want to use it as a different trait object. – dospro Apr 29 '19 at 05:00
  • 1
    You can use [`Any`](https://doc.rust-lang.org/std/any/trait.Any.html) to cast type. But can you give a scenario where this is necessary? – Peng Guanwen Apr 29 '19 at 05:22
  • I added extra context on the original question about a more realistic scenario. – dospro Apr 29 '19 at 17:19