1

This question is related to Rust: Clone and Cast Rc pointer

Let's say I have this piece of code which works fine:

use std::rc::Rc;

trait TraitAB : TraitA + TraitB {
    fn as_a(self: Rc<Self>) -> Rc<dyn TraitA>;
    fn as_b(self: Rc<Self>) -> Rc<dyn TraitB>;
}

trait TraitA {}
trait TraitB {}

struct MyType {}

impl TraitAB for MyType {
    fn as_a(self: Rc<Self>) -> Rc<dyn TraitA> {self}
    fn as_b(self: Rc<Self>) -> Rc<dyn TraitB> {self}
}

impl TraitA for MyType {}
impl TraitB for MyType {}

fn main() {
    let a: Rc<dyn TraitA>;
    let b: Rc<dyn TraitB>;
    {
        let mut ab: Rc<dyn TraitAB> = Rc::new(MyType{});
        a = ab.clone().as_a();
        b = ab.clone().as_b();
    }
    // Use a and b.
}

Explaining the code a bit:

  • I have type called MyType which implements TraitA and TraitB.
  • The goal is to have a trait object TraitA be able to get casted to TraitB and viceversa.
  • So I uses a supertrait that holds the methods to do the conversions.
  • This works great for std::Rc smart pointers.

So far so good. But now I need a mutable reference of both a and b, but since a and bare actually the same type instance, Rust won't let me have 2 mutable references of the same thing.

So, the common pattern for this kind of problems is std::cell::RefCell.

Note: I believe this pattern is correct in this particular case because it's a common interior mutability problem. I'm not willing to actually change the reference, but the internal state of the type only.

So following that idea I changed the following lines:

trait TraitAB : TraitA + TraitB {
    fn as_a(self: Rc<RefCell<Self>>) -> Rc<RefCell<dyn TraitA>>;
    fn as_b(self: Rc<RefCell<Self>>) -> Rc<RefCell<dyn TraitB>>;
}
//...
let mut ab: Rc<RefCell<dyn TraitAB>> = Rc::new(RefCell::new(MyType{}));

But this changes won't compile. After some reading, I found that self can only be:

  • self: Self // self
  • self: &Self // &self
  • self: &mut Self // &mut self
  • self: Box<Self> // No short form
  • self: Rc<Self> // No short form / Recently supported

So this means I can't use

self: Rc<RefCell<Self>>

for the self param.

So, the main question is: Is there a way to cast an Rc<RefCell<TraitA>> to an Rc<RefCell<TraitB> ? Thanks

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
dospro
  • 450
  • 1
  • 5
  • 17

1 Answers1

2

You can work around this issue by not using a receiver in TraitAB's casting methods (i.e. by declaring them as associated functions):

trait TraitAB : TraitA + TraitB {
    fn as_a(it: Rc<RefCell<Self>>) -> Rc<RefCell<dyn TraitA>>;
    fn as_b(it: Rc<RefCell<Self>>) -> Rc<RefCell<dyn TraitB>>;
}

The trait can then be implemented as

impl TraitAB for MyType {
    fn as_a(it: Rc<RefCell<MyType>>) -> Rc<RefCell<dyn TraitA>> {it}
    fn as_b(it: Rc<RefCell<MyType>>) -> Rc<RefCell<dyn TraitB>> {it}
}

These functions can then be called using the fully qualified syntax.

a = TraitAB::as_a(ab.clone());
b = TraitAB::as_b(ab.clone());

The TraitAB implementation for all types will be the same. To make this implementation available for all types implementing TraitA and TraitB, you can use a generic impl:

impl<T: TraitA + TraitB + 'static> TraitAB for T {
    fn as_a(it: Rc<RefCell<T>>) -> Rc<RefCell<dyn TraitA>> {it}
    fn as_b(it: Rc<RefCell<T>>) -> Rc<RefCell<dyn TraitB>> {it}
}

Note that T: 'static because the trait objects in the function return types have an implicit 'static lifetime bound.

Playground

EvilTak
  • 7,091
  • 27
  • 36
  • Wow. Didn't know that a trait declaration and definition can differ in their "signature". Sometimes I have this feeling that i'm looking at very obscure corners of Rust. – dospro May 03 '19 at 17:53
  • I think there is a downside with this solution. Since I can't create a TraitAB trait object, this means `MyType` needs to be known at compile time. So if I have multiple types, like `MyType1`, `MyType2`, etc, declaring a variable like this won't work: `let ab: Rc>;` Do you think there could be a work around this too? – dospro May 03 '19 at 19:10
  • @dospro To be able to make objects of a trait, it must have at least one non-static method (i.e. method with a receiver). Simply adding a method that takes `self`, `&self` or `&mut self` to the trait will let you achieve this. – EvilTak May 04 '19 at 00:56
  • I tried adding a dummy method that receives `&self` but doesn't make any difference. I tried binding Self to sized: `fn as_a(this: Rc>) -> Rc> where Self: Sized;` but again, I get: `the size for values of type `dyn TraitAB` cannot be known at compilation time` I'm out of ideas. – dospro May 04 '19 at 03:56
  • Apologies, my previous comment was inaccurate. _All_ methods, not just one or more, that a trait object can be used must be non-static. If you must use a `TraitAB` trait object, it'd be better to abstract away the usage of `RefCell` by implementing `TraitA`, `TraitB` (`T: TraitX + ?Sized`) and `TraitAB` (`T: TraitA + TraitB`) for `RefCell`, where a method impl for `RefCell` calls the corresponding method on the `RefCell` borrowed value. The trait objects will then be wrapped in just an `Rc<>` and not a `Rc>`. https://play.rust-lang.org/?gist=29bece160f90087629b0fc32a8ed0222 – EvilTak May 04 '19 at 05:36
  • can you please expand a bit on that last comment? What I understand is that I should make `T` be `RefCell` instead of just the type and let rust now it's a dynamically sized type. I've been playing with the code, but I can't get it to work yet. – dospro May 06 '19 at 03:47
  • I sent the wrong playground link in the last comment, this is the one I wanted to send: https://play.rust-lang.org/?gist=b2d98735a7602ec5fcce3afbf7d3cbc3. You've understood correctly; implement `TraitA` and `TraitB` on `RefCell` (`T: ?Sized` to allow `T` to be a trait) so that you can do `let x: &TraitA = &RefCell::new(MyType)` (i.e. cast `RefCell` to `TraitA`). This allows you to cast a `Rc>` to `Rc` where `T: TraitA`. The only problem with this approach is that the `Rc` cast hides the fact that a `RefCell` is being used. – EvilTak May 06 '19 at 04:28