rust

Downcasting of Rc<dyn Any> to Rc<GenericType<T>> fails


I am trying to write a heterogeneous storage that can retrieve differently (but strictly) typed instances. For this I am using the Any trait.

The fully reproducible example can be found on rust playground, but here's the code:

use std::any::type_name;
use std::any::Any;
use std::any::TypeId;
use std::rc::Rc;
use std::collections::HashMap;

struct GenericType<T>(T);

struct HeterogeneousStorage {
    map: HashMap<TypeId, Rc<dyn Any>>,
}

impl HeterogeneousStorage {
    pub fn add<T: 'static>(&mut self, item: Rc<GenericType<T>>) -> &mut Self {
        let ty = TypeId::of::<T>();
        self.map.insert(ty, item);
        self
    }
    
    pub fn retrieve<T: 'static>(&self) -> Rc<GenericType<T>> {
        self.map
            .get(&TypeId::of::<T>())
            .map(|item| {
                let item = item.clone();
                item.downcast_ref::<Rc<GenericType<T>>>()
                .expect("downcast failed")
                .clone()
            })
            .unwrap_or_else(|| panic!("tried to retrieve {} but it was not added", type_name::<T>()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn downcasts_correctly() {
        struct Test;
        struct Test2;
        let mut store = HeterogeneousStorage {
            map: HashMap::new()
        };
        let t1 = Rc::new(GenericType(Test));
        let t2 = Rc::new(GenericType(Test2));
        store.add(t1.clone());
        store.add(t2.clone());
        
        assert!(Rc::ptr_eq(&t1, &store.retrieve::<Test>()));
        assert!(Rc::ptr_eq(&t2, &store.retrieve::<Test2>()));
        
    }
}

The test panics with "downcast failed".

Is the problem that I am using a Rc to store the instances instead of a Box (because in the docs only Box is mentioned)? In my actual project this is an Arc<RwLock<GenericType<T>>>, so I need a solution that is Send + Sync in order to use this with async code.


Solution

  • You are downcasting the wrong thing. Following snippet will work:

    pub fn retrieve<T: 'static>(&self) -> Rc<GenericType<T>> {
        self.map
            .get(&TypeId::of::<T>())
            .map(|item| {
                let item = item.clone();
                item.downcast::<GenericType<T>>()
                .expect("downcast failed")
            })
            .unwrap_or_else(|| panic!("tried to retrieve {} but it was not added", type_name::<T>()))
    }
    

    You were using <dyn Any>::downcast_ref, and were trying to downcast it into Rc<GenericType<T>>. This cannot work, since &dyn Any is inside Rc. In other words Rc owns the trait object, so you cannot borrow it and cast it to the owning structure.

    What you need instead is just Rc::downcast, which will return an owned copy of the shared pointer. Notice, that now you specify type that you cast Rc<dyn Any> into as just GenericType<T>. This works, because shared content of all Rcs is indeed GenericType<T>.