Nugine 的个人博客关于

Rust 有趣片段(二):anymap

Any 与 TypeId 为 Rust 提供了简单的运行时反射的能力,可以用来实现任意类型容器。

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

pub struct AnyMap(HashMap<TypeId, Box<dyn Any + Send + Sync>>);

AnyMap 是对 HashMap<TypeId, Box<dyn Any> 的包装,方便起见,附带 Send,Sync 限制。

TypeId 是对 64 位无符号整数的一个包装,编译器按需对每个类型生成一个独立的 TypeId,用来标识类型。Any 是一个 trait,对所有 'static 类型实现。

impl AnyMap {
    pub fn new() -> Self {
        Self(HashMap::new())
    }

    pub fn insert<T: Any + Send + Sync>(&mut self, x: T) -> Option<T> {
        self.0
            .insert(TypeId::of::<T>(), Box::new(x))
            .map(force_downcast)
    }
    pub fn remove<T: Any + Send + Sync>(&mut self) -> Option<T> {
        self.0.remove(&TypeId::of::<T>()).map(force_downcast)
    }

    pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
        self.0
            .get(&TypeId::of::<T>())
            .map(|b| b.downcast_ref::<T>().unwrap())
    }

    pub fn get_mut<T: Any + Send + Sync>(&mut self) -> Option<&mut T> {
        self.0
            .get_mut(&TypeId::of::<T>())
            .map(|b| b.downcast_mut::<T>().unwrap())
    }
}

fn force_downcast<T: 'static>(b: Box<dyn Any + Send + Sync>) -> T {
    *<Box<dyn Any + Send>>::downcast::<T>(b).unwrap()
}

AnyMap 的操作是泛型方法,首先指定一个类型 T,AnyMap 获取 T 的 TypeId,在内部哈希表中查找 TypeId 对应的 Any 对象,取得 Any 对象后,将它转换为具体类型 T.

fn main() {
    #[derive(Debug, PartialEq, Eq)]
    struct ExtA(u32);

    #[derive(Debug, PartialEq, Eq)]
    struct ExtB(u32);

    #[derive(Debug, PartialEq, Eq)]
    struct ExtC(u32);

    let mut map = AnyMap::new();
    map.insert(ExtA(1));
    map.insert(ExtB(2));
    map.insert(ExtC(3));

    assert_eq!(map.get::<ExtA>(), Some(&ExtA(1)));
    assert_eq!(map.get_mut::<ExtB>(), Some(&mut ExtB(2)));
    assert_eq!(map.remove::<ExtC>(), Some(ExtC(3)));
}

AnyMap 能保存任意类型的至多一个实例,用法非常简单。

nodejs 的 web 框架通常会往 req 对象上添加扩展,例如 req.session. Rust 也可以在 req 内嵌入一个类似 AnyMap 的容器,用来保存不同类型的扩展对象。

dyn Any 与 Go 的 interface{} 类似,以下是 std::any 中的部分示例。

fn log<T: Any + Debug>(value: &T) {
    let value_any = value as &dyn Any;

    match value_any.downcast_ref::<String>() {
        Some(as_string) => {
            println!("String ({}): {}", as_string.len(), as_string);
        }
        None => {
            println!("{:?}", value);
        }
    }
}

基于 Any 的泛型方法可以接收任意类型,运行时判断参数的具体类型并做相应处理。

概念对比

运行时类型识别

C++: typeid / dynamic_cast /

Rust: TypeId / downcast / dyn Any

Go: TypeOf / .(type) / interface{}

动态分派

C++: 虚函数

Rust: trait

Go: 接口

泛型机制

C++: 模板 / concept

Rust: 泛型 / trait

Go:

发布于 2020-02-27地址: GitHub, 知乎