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)有利于编译器作出代码的优化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容