Сбой понижения типа Rc в Rc

Вопрос или проблема

Я пытаюсь написать гетерогенное хранилище, которое может извлекать экземпляры с разными (но строго) типами. Для этого я использую трейты 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>>>, также применены аналогичные принципы, чтобы гарантировать получение блокировок и доступ к данным безопасным образом.

Если у вас возникнут дополнительные вопросы по данной теме или будут необходимые уточнения, не стесняйтесь спрашивать!

Оцените материал
Добавить комментарий

Капча загружается...