rust - 如何在保持相同 API 的同时用枚举替换 Any 的盒装特征对象?

标签 rust

我目前使用以下模式在容器中存储多个不同的用户定义类型。

use std::any::{Any, TypeId};
use std::collections::HashMap;

#[derive(Default)]
struct Container {
    inner: HashMap<TypeId, Box<Any>>,
}

impl Container {
    pub fn insert<T: Any>(&mut self, data: T) {
        self.inner.insert(TypeId::of::<T>(), Box::new(data));
    }
    pub fn borrow<T: Any>(&self) -> Option<&T> {
        self.inner.get(&TypeId::of::<T>())
            .and_then(|a| a.downcast_ref::<T>())
    }
}

现在,我想不再使用盒装特征对象,而是使用枚举代替它们(类型参数 G),但要保持相同的面向外的 API。这里的目标是通过使用一个枚举来绕过虚拟化,该枚举封装了每个已知的用户提供的类型T:

use std::any::{Any, TypeId};
use std::collections::HashMap;

struct Container<G> {
    inner: HashMap<TypeId, G>,
}

impl<G> Default for Container<G> {
    fn default() -> Self {
        Container {
            inner: Default::default(),
        }
    }
}

impl<G> Container<G> {
    pub fn insert<T: Any + Into<G>>(&mut self, data: T) {
        self.inner.insert(TypeId::of::<T>(), data.into());
    }
    pub fn borrow<T: Any>(&self) -> Option<&T> {
        unimplemented!()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// This should be an user-defined type that implements the Any trait.
    #[derive(Debug, Clone, PartialEq)]
    struct TypeA(u32);

    /// This should be an user-defined type that implements the Any trait.
    #[derive(Debug, Clone, PartialEq)]
    struct TypeB(String);

    /// This is the enum that should replace boxed `Any` trait objects. Users also need to supply
    /// this enum. Maybe they'll need to implement additional traits to get `borrow` to work.
    #[derive(Debug, PartialEq)]
    enum Group {
        A(TypeA),
        B(TypeB),
    }

    impl From<TypeA> for Group {
        fn from(value: TypeA) -> Self {
            Group::A(value)
        }
    }

    impl From<TypeB> for Group {
        fn from(value: TypeB) -> Self {
            Group::B(value)
        }
    }

    #[test]
    fn insert() {
        let mut c: Container<Group> = Default::default();
        let data = TypeA(100);
        c.insert(data.clone());
        assert_eq!(
            c.inner.get(&TypeId::of::<TypeA>()),
            Some(&Group::A(data.clone()))
        );
    }

    #[test]
    fn borrow() {
        let mut c: Container<Group> = Default::default();
        let data = TypeA(100);
        c.insert(data.clone());
        let borrowed = c.borrow::<TypeA>();
        assert_eq!(borrowed, Some(&data));
    }
}

我将如何着手实现 borrow 方法?非常感谢您的帮助!

最佳答案

我可以使用 Any 上的 downcast_ref 方法从实现 GroupTrait 的枚举向下转型:

use std::any::{Any, TypeId};
use std::collections::HashMap;

trait GroupTrait {
    fn borrow<T: Any>(&self) -> Option<&T>;
}

struct Container<G> {
    inner: HashMap<TypeId, G>,
}

impl<G> Default for Container<G>
where
    G: GroupTrait,
{
    fn default() -> Self {
        Container {
            inner: Default::default(),
        }
    }
}

impl<G> Container<G>
where
    G: GroupTrait,
{
    pub fn insert<T: Any + Into<G>>(&mut self, data: T) {
        self.inner.insert(TypeId::of::<T>(), data.into());
    }
    pub fn borrow<T: Any>(&self) -> Option<&T> {
        self.inner.get(&TypeId::of::<T>()).and_then(|g| g.borrow())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// This should be an user-defined type that implements the Any trait.
    #[derive(Debug, Clone, PartialEq)]
    struct TypeA(u32);

    /// This should be an user-defined type that implements the Any trait.
    #[derive(Debug, Clone, PartialEq)]
    struct TypeB(String);

    /// This is the enum that should replace boxed `Any` trait objects. Users also need to supply
    /// this enum. Maybe they'll need to implement additional traits to get `borrow` to work.
    #[derive(Debug, PartialEq)]
    enum Group {
        A(TypeA),
        B(TypeB),
    }

    impl From<TypeA> for Group {
        fn from(value: TypeA) -> Self {
            Group::A(value)
        }
    }

    impl From<TypeB> for Group {
        fn from(value: TypeB) -> Self {
            Group::B(value)
        }
    }

    impl GroupTrait for Group {
        fn borrow<T: Any>(&self) -> Option<&T> {
            use self::Group::*;
            match *self {
                A(ref i) => Any::downcast_ref(i),
                B(ref i) => Any::downcast_ref(i),
            }
        }
    }

    #[test]
    fn insert() {
        let mut c: Container<Group> = Default::default();
        let data = TypeA(100);
        c.insert(data.clone());
        assert_eq!(
            c.inner.get(&TypeId::of::<TypeA>()),
            Some(&Group::A(data.clone()))
        );
    }

    #[test]
    fn borrow() {
        let mut c: Container<Group> = Default::default();
        let data = TypeA(100);
        c.insert(data.clone());
        let borrowed = c.borrow::<TypeA>();
        assert_eq!(borrowed, Some(&data));
    }
}

关于rust - 如何在保持相同 API 的同时用枚举替换 Any 的盒装特征对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49457323/

相关文章:

casting - 如何安全地、惯用地在数字类型之间进行转换?

Rust 程序偶尔会给出不一致的结果

rust - 是否可以有条件地启用 `derive` 之类的属性?

rust - 如何在不复制矢量的情况下将 Vec<T> 转换为 Vec<U>?

rust - 如何在嵌套匹配语句中多次访问可变向量?

rust - 通过借用它遍历大小合适的Range <T>

打印后 Rust 从控制台读取输入

r - Polars Rust Melt() 明显慢于 R stack()

rust - 如何使用 Rust 编译/检查目录中的所有文件?

rust - [Rust 枚举] : How to get data value from mixed type enum in rust?