1

I have traits Child and Super where Super is a supertrait of Child. I have a function that will get an instance of Rc<RefCell<dyn Child>> as a parameter, but I need to pass it to a function that needs a Rc<RefCell<dyn Super>> as parameter. The following code should illustrate the situation: (I'm trying to implement the use_child function such that it will call the use_parent function):

use std::{rc::Rc, cell::RefCell};

fn use_parent(parent: Rc<RefCell<dyn Super>>) {
    // Use the parent functionality
}

fn use_child(child: Rc<RefCell<dyn Child>>) {
    // I tried this, but it won't compile
    use_parent(child as Rc<RefCell<dyn Super>>);
}

trait Super {}

trait Child: Super {}

The compiler gives the following complaint:

error[E0605]: non-primitive cast: `std::rc::Rc<std::cell::RefCell<(dyn Child + 'static)>>` as `std::rc::Rc<std::cell::RefCell<dyn Super>>`
 --> src/lib.rs:9:16
  |
9 |     use_parent(child as Rc<RefCell<dyn Super>>);
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

I read the suggestion of the compiler, but could only come up with use_parent(Rc::from(child)), which won't compile either.

From the answers of the related question (Why doesn't Rust support trait object upcasting?), it appears that doing what I'm trying is not (directly?) possible due to vtable problems. Another answer of that question also showed the following work-around:

trait Base: AsBase {
    // ...
}

trait AsBase {
    fn as_base(&self) -> &Base;
}

impl<T: Base> AsBase for T {
    fn as_base(&self) -> &Base {
        self
    }
}

Unfortunately, I can't just use this work-around because everything is wrapped inside Rc and RefCell, which makes my question different from the other question.

The best work-around I could come up with was passing both a Rc<RefCell<dyn Child>> and a Rc<RefCell<dyn Super>> to the use_child function where both Rc's point to the exact same cell. This would solve the problem, but requires me to always keep an extra Rc. Besides, this feels quite dirty.

So does anybody know a better solution?

Knokko
  • 88
  • 1
  • 8
  • tl;dr the duplicate: cross-casting between traits is not supported because trying to do so raises a lot of questions about vtable layout and so on; however, you can add a method that does so on a trait by trait basis. – trent Oct 27 '19 at 22:21
  • 1
    Speculatively, I think you might be trying to translate a class-based design into Rust. There are a lot of cases where in say Java you might use inheritance but in Rust, which has a more expressive type system and more flexible generics (but no inheritance), are better suited for another approach. [Here's a recent users.rust-lang.org thread that lists some ideas you might find interesting.](https://users.rust-lang.org/t/how-to-model-inheritance-hierarchy/33380) – trent Oct 27 '19 at 22:29
  • @trentcl You mentioned that I could add a method that does so on a trait by trait basis. How would you go on about doing this? Unlike the other question, the child value is not just behind an &-reference, but wrapped inside Rc and RefCell. I can't just take the child value out and put it in a new wrapper because it is not Sized and its might already be (mutably) borrowed. – Knokko Oct 28 '19 at 17:56
  • I'm really not sure. You may have to change `use_parent` as well as `use_child`. I'll try to take a deeper look into it. Some more information about how you're using `Super` and `Child` might help; maybe you're trying to express something that just doesn't map to traits very well, but it's hard to say. – trent Oct 30 '19 at 02:00
  • @trentcl I must have misunderstood your first comment, I thought you meant that it could easily be done on trait-by-trait basis but that I failed to find it. But if that is not the case, there is 1 work-around I came up with: I can give the use_child function 2 parameters: 1 for Rc> and the other one for the Super cell. When calling the function, I would just let the second parameter be a clone of the first one. This solution can easily be translated to my real situation and it allows me to split Child from Super later on if I would want to. – Knokko Oct 30 '19 at 17:36
  • You're right, my earlier comment was misleading. I still think this can probably be done in some other way similar to the other question; however, I haven't really dug into it. Do you think this question should be reopened or do the answers to the linked duplicate sufficiently answer your original question? – trent Oct 30 '19 at 17:40
  • @trentcl I'm not sure whether or not this should be re-opened. This question is almost the same as the other question, except that the work-arounds are much harder due to the Rc> wrapping. Also, I am still thinking about your comment about the class-based design. Even though Rust is the first language I'm implementing this pattern in and there aren't really classes, this is some kind of interface inheritance. On the other hand, this approach is currently only blocked by a vtable imlementation problem rather than the language design of Rust. – Knokko Oct 30 '19 at 18:50
  • @trentcl Yes, I would like this question to be reopened and edited the question to explain how it is different from the related question. However, I don't have the reputation to (request to) reopen questions. From what I have read, it should automatically be put in some review queue, but I don't have the reputation to see those either. – Knokko Nov 04 '19 at 20:21

0 Answers0