Вопрос или проблема
Я пытаюсь написать гетерогенное хранилище, которое может извлекать экземпляры с разными (но строго) типами. Для этого я использую трейты Any
.
Полностью воспроизводимый пример можно найти на rust playground, но вот код:
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("провал приведения типа")
.clone()
})
.unwrap_or_else(|| panic!("попытка извлечь {} но он не был добавлен", 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>()));
}
}
Тест выдает панику с “провал приведения типа”.
В чем проблема? Я использую Rc
для хранения экземпляров вместо Box
(потому что в документации упоминается только Box
)? В моем реальном проекте это Arc<RwLock<GenericType<T>>>
, так что мне нужно решение, которое подойдет для Send + Sync
, чтобы использовать это с async
кодом.
Вы пытаетесь сделать приведение типа не к тому объекту. Следующий фрагмент будет работать:
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("провал приведения типа")
})
.unwrap_or_else(|| panic!("попытка извлечь {} но он не был добавлен", type_name::<T>()))
}
Вы использовали <dyn Any>::downcast_ref
, и пытались привести его к Rc<GenericType<T>>
. Это не может сработать, так как &dyn Any
находится внутри Rc
. Другими словами, Rc
владеет объектом трейта, поэтому вы не можете занять его и привести к принадлежащей структуре.
Вам нужно просто Rc::downcast
, который вернет собственную копию общего указателя. Обратите внимание, что теперь вы указываете тип, в который приводите Rc<dyn Any>
как просто GenericType<T>
. Это сработает, потому что общий контент всех Rc
действительно является GenericType<T>
.
Ответ или решение
Проблема, с которой вы столкнулись, связана с неправильным использованием метода для приведения типов в вашей реализации. Давайте разберем, что происходит в вашей функции retrieve
и как можно исправить эту ошибку.
Ваша изначальная реализация функции retrieve
использует downcast_ref
, который предназначен для получения ссылки на объект, если он имеет указанный тип. Однако в вашем случае вы пытаетесь сделать приведение типов из Rc<dyn Any>
в Rc<GenericType<T>>
, что невозможно, поскольку Rc
является оберткой над трейт-объектом, а вы пытаетесь получить доступ к внутреннему содержимому, не имея на это прав.
Чтобы исправить код, вам нужно использовать метод Rc::downcast
, который позволяет выполнить безопасное приведение типа Rc<dyn Any>
к Rc<GenericType<T>>
. Таким образом, вы получите пример, где вы можете безопасно получить Rc
для вашего конкретного типа. Вот исправленный код вашей функции retrieve
:
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>()))
}
В этом коде мы используем метод downcast::<GenericType<T>>()
, который будет пытаться привести тип Rc<dyn Any>
к типу Rc<GenericType<T>>
. Если приведение не удалось, будет вызвано паническое исключение с сообщением "downcast failed".
Таким образом, устранен источник ошибки, и теперь ваш код будет корректно извлекать экземпляры GenericType<T>
из хранилища. Убедитесь, что в вашем основном проекте, где используется Arc<RwLock<GenericType<T>>>
, также применены аналогичные принципы, чтобы гарантировать получение блокировок и доступ к данным безопасным образом.
Если у вас возникнут дополнительные вопросы по данной теме или будут необходимые уточнения, не стесняйтесь спрашивать!