Upcasting reference from &Rc<RefCell<Derived: T>> to &Rc<RefCell<dyn T>>

Hi, there, I am learning Rust and start to build a small program. The concept is from my experience in C++: shared the reference and interior mutability with trait implementation. Therefore, I use Rc<RefCell<Derived: T>> to store derived objects and call the trait function to get the reference to trait object (&dyn T). However, the attempt was not successful. I also tried several other ways like return &RefCell<&dyn T>> and still failed. Here is the code with behaviors that I expected. It's more like a C++ concept, but can't figure out how to make it work in Rust. Struct A and B are both derived from trait T. Struct C and D owns A and B, respectively. In main function, I use trait T object to call foo(). Looking forward to suggestions. Thanks.

Sean

use std::cell::RefCell;
use std::rc::Rc;

pub trait T {
    fn foo(&self) -> u32;
}

struct A {
}

impl T for A {
    fn foo(&self) -> u32 {
        1
    }
}

struct B {
}

impl T for B {
    fn foo(&self) -> u32 {
        2
    }
}

struct C {
    a: Rc<RefCell<A>>
}

impl C {
    fn get_t(&mut self) -> &dyn T {
        // error[E0605]: non-primitive cast: `Ref<'_, A>` as `&dyn T`
        self.a.borrow() as &dyn T
    }
}

struct D {
    b: Rc<RefCell<B>>
}

impl D {
    fn get_t(&mut self) -> &dyn T {
        // error[E0605]: non-primitive cast: `Ref<'_, A>` as `&dyn T`
        self.b.borrow() as &dyn T
    }
}

fn main() {
    let a = A {};
    let b = B {};
    let mut c = C {
        a: Rc::new(RefCell::new(a))
    };
    let mut d = D {
        b: Rc::new(RefCell::new(b))
    };
    let mut t = c.get_t();
    println!("{:?}", t.foo()); // 1
    t = d.get_t();
    println!("{:?}", t.foo()); // 2
}

You can return &RefCell<dyn T> if you get the type lined up right:

impl C {
    fn get_t(&self) -> &RefCell<dyn T + 'static> {
        &*self.a
    }
}

You have to use an explicit * dereference to get past the Rc to a &RefCell<A> before you can coerce it to dyn. And you can't return an &dyn T because you can never return a reference to the contents of a RefCell (unless you have exclusive access to it, which you don't necessarily have here since you are using Rc, and usually but not always defeats the point of using RefCell).

Also, please don't name traits or types with single letters — it's very confusing. In standard Rust style, single-letter capital names should be reserved for type parameters (or constants) only.

2 Likes

Oh, it would also be possible to return Rc<RefCell<dyn T>> if you wanted:

impl C {
    fn get_t(&mut self) -> Rc<RefCell<dyn T + 'static>> {
        self.a.clone()
    }
}

When I said “have to dereference” I meant for the purpose of getting an & reference to the cell in particular.

1 Like

Sorry for the name of the structs, just want to illustrate the scenario. This is the solution to solve this problem since I only need the reference to Refcell<dyn T>. Rc::clone() is another way.
Casting from Rc<RefCell<Derived: T>> to Rc<RefCell<dyn T>> is really fantastic. The type Derived was wrapped twice by Rc and RefCell and type coercion still can be applied. Unbelievable! Thank you for the prompt assistance.

Being so nested often is a restriction -- you can't cast a Vec<Box<Ty>> to a Vec<Box<dyn Trait>> for example, because Box<Ty> and Box<dyn Trait> have different layouts. But it's possible for inline wrappers like Cell<T> and RefCell<T> to not count as a "nesting layer" for the purposes of unsizing coercions, as they store the value of T inline, not behind indirection.

Thanks for further explanations. Is Rc<RefCell<T>> the standard way to implement reference-counted object with interior mutability in Rust?

Rc<_> or Arc<_> are the standard reference-counted (shared ownership) primitives. If you need the ability to produce a &mut T, the standard approaches are Rc<RefCell<T>> for single-threaded or Arc<Mutex<T>> or Arc<RwLock<T>> for multi-threaded. There are also third-party versions of some of these, like in the parking_lot and tokio (for locks held across awaits) crates.

If by "object" you mean a dyn Trait speficically, there may be other considerations; you can't coerce a Mutex<T> to a Mutex<dyn Trait> for example. I'm not sure what's most common to work around this if needed, but it probably involves implementing Trait for some type of smart pointer(s) (Mutex<impl ?Sized + Trait> or &Mutex<...> or Arc<Mutex<...>> or newtypes or...).

1 Like