How to downcast Rc<RefCell<dyn Trait>>?

Is there a safe way to downcast Rc<RefCell<dyn Trait>> to Rc<RefCell<ConcreteType>>? There is a downcast method for Rc<dyn Any> but I don't see any ways to achieve what I want.

Otherwise, is it sound to use the unsafe methods Rc::from_raw and Rc::into_raw together with a pointer cast to achieve what I want? I prefer a safe way first and foremost but if that cannot be done in the current state of Rust, I'm fine with going down to use unsafe.

Thanks!

1 Like

RefCell<ConcreteType> implements Any, so one approach would be to store that inside Rc<dyn Any> and then use Rc::downcast::<RefCell<ConcreteType>>> to recover the cell.

Alternatively, if Trait: Any, you can borrow the RefCell and downcast the resulting reference with Any::downcast or Any::downcast_mut:

use std::any::{Any,TypeId};
use std::cell::{RefCell, Ref, RefMut};

pub fn borrow_downcast<T: Any>(cell: &RefCell<dyn Any>)->Option<Ref<T>> {
    let r = cell.borrow();
    if (*r).type_id() == TypeId::of::<T>() {
        Some(Ref::map(r, |x| x.downcast_ref::<T>().unwrap()))
    } else {
        None
    }
}

pub fn borrow_downcast_mut<T: Any>(cell: &RefCell<dyn Any>)->Option<RefMut<T>> {
    let r = cell.borrow_mut();
    if (*r).type_id() == TypeId::of::<T>() {
        Some(RefMut::map(r, |x| x.downcast_mut::<T>().unwrap()))
    } else {
        None
    }
}
2 Likes

Thanks for your answer, but my actual problem is a little more complicated than that :frowning_face:

In actuality, I have a Rc<RefCell<Wrapper<dyn Trait>>> where the Wrapper struct provides some metadata. A good number of times, I will only need to access the fields on Wrapper so it is overkill to use approach (1) you suggested.

In the cases where I actually need to downcast, the entire type Rc<RefCell<Wrapper<ConcreteType>>> is needed as I need to clone the reference at the same time be able to access the underlying type.

Such is why I asked the original question. It is likely there is no safe way to go about doing this.

If anybody can double check whether this is sound (using Rc::from_raw and Rc::into_raw to convert Rc<RefCell<Wrapper<dyn Trait>>> to Rc<RefCell<Wrapper<ConcreteType>>>), I'll be happy to just go along with the unsafe method.

If Rc<RefCell<Wrapper<ConcreteType>>> can coerce to Rc<RefCell<Wrapper<dyn Trait>>> then, indeed, I believe that "transmuting" the Rc through {from,into}_raw funneling is sound:

use ::std::{
    any::{Any, TypeId},
    cell::RefCell,
    rc::Rc,
};

trait Trait : Any { … }

type SharedWrapper<T> = Rc<RefCell<Wrapper<T>>>;

fn try_downcast_rc_refcell_wrapper<T : Trait> (
    rc: SharedWrapper<dyn Trait>,
) -> Result<
        SharedWrapper<T>,
        SharedWrapper<dyn Trait>,
    >
{
    if <dyn Trait>::type_id(&rc.borrow().wrappee) == TypeId::of::<T>() {
        Ok(unsafe {
            fn _sanity_check (rc: SharedWrapper<impl Trait>)
              -> SharedWrapper<dyn Trait>
            {
                rc // Unsize coercion passes.
            }

            Rc::from_raw(Rc::into_raw(rc) as *const RefCell<Wrapper<T>>)
        })
    } else {
        Err(rc)
    }
}
3 Likes

It’s unfortunate that this still requires accessing the RefCell, even though, technically, the necessary metadata in order to determine the TypeId is already part of the Rc<RefCell<Wrapper<dyn Trait>>>.

I’m not aware of any sound way to avoid this currently in stable Rust. With feature(arbitrary_self_types), we could use a *const Self receiver type for an alternative to the Any trait in order to do the job:

#![feature(arbitrary_self_types)]

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

struct Wrapper<T: ?Sized> {
    extra_info: u8,
    wrappee: T,
}

trait Trait: MyAny {}

trait MyAny: 'static {
    fn type_id(self: *const Self) -> TypeId {
        TypeId::of::<Self>()
    }
}
impl<T: ?Sized + 'static> MyAny for T {}

type SharedWrapper<T> = Rc<RefCell<Wrapper<T>>>;

fn try_downcast_rc_refcell_wrapper<T: Trait>(
    rc: SharedWrapper<dyn Trait>,
) -> Result<SharedWrapper<T>, SharedWrapper<dyn Trait>> {
    // Doing this inline results in error, apparently rust tries to do unsized coercion
    // instead of pointer casts. Anyway, this works.
    fn cast_unsized<T: ?Sized>(x: *const RefCell<Wrapper<T>>) -> *const T {
        x as _ // metadata of *const RefCell<Wrapper<T>> and *const T is the same,
               // pointee doesn’t have to be valid for `.type_id()` call
    }
    if cast_unsized(Rc::as_ptr(&rc)).type_id() == TypeId::of::<T>() {
        Ok(unsafe {
            fn _sanity_check(rc: SharedWrapper<impl Trait>) -> SharedWrapper<dyn Trait> {
                rc // Unsize coercion passes.
            }

            Rc::from_raw(Rc::into_raw(rc) as *const RefCell<Wrapper<T>>)
        })
    } else {
        Err(rc)
    }
}

(playground)

3 Likes

Thanks for your answers! I've gone along with {from,into}_raw method.

Sidetracking, for curiosity's sake, I wonder if there had been any suggestions in the past to deal with this sort of problems. Would be illuminating to keep up with what can be done about this in a safe and language-supported manner.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.