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.
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 Rc
s is indeed GenericType<T>
.