Rust std-any 模块详解

原文

1. 简介

反射reflection意味着可以在运行时获得类型的所有详细信息,包括字段方法等,并可以进行替换。rust只有“compile-time reflection”,和java不同,java运行在虚拟机之上,拥有“runtime reflection”,关于rust为什么不引入运行时反射,有很多相关讨论,在此不进行赘述

rust目前的反射功能比较弱,只有any可以算是起到了部分反射的功能,不过社区有人实现了利用过程宏reflect实现的编译时反射功能,以实现依赖注入等反射功能。

std:any起到的作用有4个

  • 获得变量的类型TypeId
  • 判断变量是否是指定类型
  • 把any转换成指定类型
  • 获取类型的名字

2. 关键内容

// 每一个类型都有自己的全局唯一的标志符`
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]`
#[stable(feature = "rust1", since = "1.0.0")]`
pub struct TypeId {
    t: u64,
}
// 调用了intrinsic库里的函数(ps,forget,size_of也在里面),是内建的类型,方便语言内部功能的实现。
impl TypeId {
  #[stable(feature = "rust1", since = "1.0.0")]
  #[rustc_const_unstable(feature="const_type_id")]
  pub const fn *of*<T: ?Sized + *'static*>() -> TypeId {
        TypeId {
            #[cfg(not(bootstrap))]
            t: intrinsics::type_id::<T>(),
        }
    }
}
// 所有拥有静态生命周期的类型都会实现Any,未来可能会考虑加入生命周期是非‘static的情况
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Any: 'static {
    #[stable(feature = "get_type_id", since = "1.34.0")]
    fn type_id(&self) -> TypeId;

// 主要内容1.获得变量的类型TypeId
// 为所有的T实现了Any
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: 'static + ?Sized > Any for T {
    fn type_id(&self) -> TypeId { TypeId::of::<T>() }
}}
 

// 主要内容2.判断变量是否是指定类型
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
pub fn is<T: Any>(&self) -> bool {
    // Get `TypeId` of the type this function is instantiated with.
    let t = TypeId::of::<T>();

    // Get `TypeId` of the type in the trait object.
    let concrete = self.type_id();

    // Compare both `TypeId`s on equality.
    t == concrete
}
 

// 主要内容3.把any转换成指定类型
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
    if self.is::<T>() {
        // SAFETY: just checked whether we are pointing to the correct type
        unsafe {
            Some(&*(self as *const dyn Any as *const T))
        }
    } else {
        None
    }
}

// 主要内容4.获取类型名字
pub const fn type_name<T: ?Sized>() -> &'static str {
    intrinsics::type_name::<T>()
}

关于type_id的生成,rust最早的版本使用的是

self.const_u64(self.tcx.type_id_hash(substs.type_at(0)))

现在的版本使用的是

let ty_name = self`
 `.tcx`
 `.const_eval_instance(ty::ParamEnv::reveal_all(), instance, None)`
 `.unwrap();`
 `OperandRef::from_const(self, ty_name).immediate_or_packed_pair(self)

具体地址

https://github.com/rust-lang/rust/blob/8a87b945b27b5670ac5ed665bbb0fccc1b88a0a0/src/librustc_codegen_llvm/intrinsic.rs#L231

3. 使用示例

//1 获取Type_id
use std::any::{Any, TypeId};
fn is_string(s: &dyn Any) -> bool {
     TypeId::of::<String>() == s.type_id()
}
//2.判断是否是指定类型
use std::any::Any;
fn is_string(s: &dyn Any) {
     if s.is::<String>() {
         println!("It's a string!");
     } else {
         println!("Not a string...");
     }
}
//3.转换any为特定类型
use std::any::Any;
fn print_if_string(s: &dyn Any) {
     if let Some(string) = s.downcast_ref::<String>() {
         println!("It's a string({}): '{}'", string.len(), string);
     } else {
         println!("Not a string...");
     }
} 
//4.获取类型的名字
//通过此函数获得的名字不唯一,比如type_name::<Option<String>>()可能返回"Option<String>"
//或"std::option::Option<std::string::String>",同时编译器版本不同返回值可能不同
assert_eq!(
    std::any::type_name::<Option<String>>(),
    "core::option::Option<alloc::string::String>",
);

4. 适用场景

  • 针对需要函数重载的场景,可以不用范型,也不用多次定义函数,可以使用any
use std::any::Any;
use std::fmt::Debug ;

fn load_config(value: &dyn Any) -> Vec<String>{
    let mut cfgs: Vec<String>= vec![];
    match value.downcast_ref::<String>() {
        Some(cfp) => cfgs.push(cfp.clone()),
        None => (),
    };

    match value.downcast_ref::<Vec<String>>() {
        Some(v) => cfgs.extend_from_slice(&v),
        None =>(),
    }

    if cfgs.len() == 0 {
        panic!("No Config File");
    }
    cfgs
}

fn main() {
    let cfp = "/etc/wayslog.conf".to_string();
    assert_eq!(load_config(&cfp), vec!["/etc/wayslog.conf".to_string()]);
    let cfps = vec!["/etc/wayslog.conf".to_string(),
                    "/etc/wayslog_sec.conf".to_string()];
    assert_eq!(load_config(&cfps),
               vec!["/etc/wayslog.conf".to_string(),
                    "/etc/wayslog_sec.conf".to_string()]);
}

5. 额外的话

  • any特性实际上不是大多数人认为的“反射”。勉强说的话是编译时反射,因为rust只是启用类型检查和类型转换,而不是检查任意结构的内容。
  • any符合零成本抽象。因为rust只会针对调用相关函数的类型生成代码,并且判断类型时返回的是编译器内部的类型ID,没有额外的开销。甚至可以直接使用TypeId::of::<String>(),没有了dyn any的动态绑定的开销。
  • 过程宏可以实现大部分反射能够实现的功能,后面可以再次阅读一下相关库代码。
  • rust的早期版本拥有reflection功能(早于1.0版本发布),但是在14年移除了相关代码,总结下来原因是,1)反射打破了封装的原则,可以任意访问结构体的内容,不安全;2)反射的存在使得代码过于臃肿,如果移除那么编译器会简化很多;3)反射功能设计的比较弱,开发者对于是否在未来的版本中还拥有反射功能存疑,因此最简单的方法就是去掉。最终结果就是现如今反射仅剩下了any这一部分内容。
  • 至于为什么要保留any,原因是1)在调试范型类型相关的代码的时候,有typeid会更方便,更容易给出正确的错误提示;2)有利于编译器作出代码的优化
Last modification:April 15th, 2021 at 08:39 pm
安安,亲觉得有用, 请随意打赏嗷