2

So far I have rarely had issues with Rust's type inference, but I fear I don't quite understand the problem with the following code:

trait SpecificTrait {}

struct SpecificStruct;

impl SpecificTrait for SpecificStruct {}

trait GeneralTrait {}

impl<T: SpecificTrait> GeneralTrait for T {}

fn new_specific_box() -> Box<dyn SpecificTrait> {
    Box::new(SpecificStruct {})
}

fn new_general_box(from_specific_box: bool) -> Box<dyn GeneralTrait> {
    if from_specific_box {
        new_specific_box()
    } else {
        Box::new(SpecificStruct {})
    }
}

Playground

I assume it has to do with Rust probably still not supporting upcasting, though in this code SpecificTrait does not require GeneralTrait, but rather implements the more general trait generically over all types that implement SpecificTrait.

I am aware that the trait object types are different (which leads to the error in the above code), but I would expect type inference to acknowledge that every dyn SpecificTrait object should also be expressable as a dyn GeneralTrait object. However, I also cannot simply cast a Box<dyn SpecificTrait> as Box<dyn GeneralTrait>, either.

So, how would I (idomatically) have to go about re-expressing my Box<dyn SpecificTrait> as a Box<dyn GeneralTrait>?

Aiden4
  • 2,504
  • 1
  • 7
  • 24
Sty
  • 760
  • 1
  • 9
  • 30

3 Answers3

4

I would expect type inference to acknowledge that every dyn SpecificTrait object should also be expressable as a dyn GeneralTrait object

But it isn't. A dyn SpecificTrait includes a pointer to the "virtual table" of function pointers to SpecificTrait methods, and you can't get a pointer to the corresponding virtual table for GeneralTrait from it. One of answers to the question you linked explains the problem for subtraits in detail, but

implements the more general trait generically over all types that implement SpecificTrait

makes this even worse. With subtraits, the methods of supertraits are at least present in the subtrait vtable (24- |methods of Self and supertraits in that answer). With the blanket implementation they aren't.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • I admit that I'm not involved enough in language/compiler design to have a good understanding of how vtables work (or don't, in this case), so I have a hard time understanding how the generic trait implementation compunds the issue here. But either way, while I appreciate you pointing out why it's not as straightforward to resolve as I'd hoped it would be, your answer still falls short of providing suggestions as to how developers should (or simply commonly would) address this issue. In my case I ended up following the top answer of the linked question, but not sure if that's idiomatic. – Sty May 05 '21 at 09:17
  • I've added an explanation why a blanket implementation compounds the issue, but unfortunately I don't know a particularly good solution. I don't think that's a common enough need to be idiomatic or not, but if it works for your requirements, good enough. – Alexey Romanov May 05 '21 at 10:07
1

Another answer explained why you can't simply cast the trait object to get the result you want. However, there is a workaround:

impl SpecificTrait for Box<dyn SpecificTrait>{}
fn new_general_box(from_specific_box: bool) -> Box<dyn GeneralTrait> {
    if from_specific_box {
        Box::new(new_specific_box())
    } else {
        Box::new(SpecificStruct {})
    }
}

In words, simply implement your specific trait for a boxed trait object, then box that. Not the most efficient, but it will work.

Playground

Aiden4
  • 2,504
  • 1
  • 7
  • 24
0

Maybe I did not get the deeper problem you are trying to express, but from the code, the reason why it is not compile is bacause the return type of the function fn new_specific_box() -> Box<dyn SpecificTrait> is Box<dyn SpecificTrait> where as you are expecting Box<dyn GeneralTrait>. These two are different types, so this code will not compile. If you can match the type, then it should be okay to compile.

LMJWILL
  • 171
  • 1
  • 9
  • Well, but that's the thing: is there a way to "match the type" by any means of direct conversion, or is the only way the one in the `else` branch, where I would have to instantiate a concrete struct first, perhaps perform methods associated with `SpecificTrait` on that object and only once I need it box it as a `dyn GeneralTrait`? Because that would make storing `SpecificTrait` objects of different concrete types alongside one another quite inconvenient if further downstream I just want to use them as `GeneralTrait` objects... – Sty May 04 '21 at 13:40