Rust std-any 模块详解
原文¶
https://www.jianshu.com/p/28386b52c607
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最早的版本使用的是
现在的版本使用的是
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)
具体地址
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::
(),没有了dyn any的动态绑定的开销。 - 过程宏可以实现大部分反射能够实现的功能,后面可以再次阅读一下相关库代码。
- rust的早期版本拥有reflection功能(早于1.0版本发布),但是在14年移除了相关代码,总结下来原因是,1)反射打破了封装的原则,可以任意访问结构体的内容,不安全;2)反射的存在使得代码过于臃肿,如果移除那么编译器会简化很多;3)反射功能设计的比较弱,开发者对于是否在未来的版本中还拥有反射功能存疑,因此最简单的方法就是去掉。最终结果就是现如今反射仅剩下了any这一部分内容。
- 至于为什么要保留any,原因是1)在调试范型类型相关的代码的时候,有typeid会更方便,更容易给出正确的错误提示;2)有利于编译器作出代码的优化