前语

关于Rust为何不引入 Runtime Reflection 可以参阅这个 RFC:

  • internals.rust-lang.org/t/pre-rfc-r…

大致总结如下:

DI 不一定非要运用反射来完成, Rust中可以有更好的完成:

派生宏和Trait之间的合作,可以将完成从运行时转移到编译时;

例如,运用进程宏完成了编译时反射功用,以完成依赖注入等反射功用:

  • github.com/dtolnay/ref…

Rust 中供给了AnyTrait:所有类型(含自界说类型)都主动完成了该特征;

因此,咱们可以经过它进行一些类似反射的功用;

Any解析

下面是std::any模块的阐明:

该模块完成了Anytrait,它可以经过运行时反射来动态键入任何'static类型;Any自身可以用来获取TypeId,并用作 trait 对象时具有更多功用;

作为&dyn Any(借用的 trait 对象),它具有isdowncast_ref办法,以测验所包括的值是否为给定类型,并对该类型的内部值进行引证;作为&mut dyn Any,还有downcast_mut办法,用于获取内部值的变量引证;

Box<dyn Any>添加了downcast办法,该办法测验转化为Box<T>注意,&dyn Any仅限于测验值是否为指定的详细类型,而不能用于测验某个类型是否完成 Trait;

总结如下,std::any起到的作用有4个:

  • 取得变量的类型TypeId;
  • 判别变量是否是指定类型;
  • 把any转化成指定类型;
  • 获取类型的姓名;

下面是 Any Trait 的源码,以及对应的 TypeId 类型:

pub trait Any: 'static {
    fn type_id(&self) -> TypeId;
}
// 取得变量的类型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>() }
}
// 判别变量是否是指定类型
#[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
}
// 把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
    }
}
// 获取类型姓名
pub const fn type_name<T: ?Sized>() -> &'static str {
    intrinsics::type_name::<T>()
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct TypeId {
    t: u64,
}

注意:所有拥有静态生命周期的类型都会完成Any,未来或许会考虑参加生命周期对错‘static的状况

在 Rust 中,每个类型都存在一个全局仅有的标识(ATypeIdrepresents a globally unique identifier for a type);

这些 TypeId 经过调用 intrinsic 模块中界说的函数来完成创立;

关于intrinsic 模块:

intrinsic 库函数是指:由编译器内置完成的函数,一般是具有如下特色的函数:

  • 与CPU架构相关性很大,必须运用汇编完成或许运用汇编才干具备最高性能的函数;
  • 和编译器密切相关的函数,由编译器来完成最为合适;

因此,type_id 的生成是由编译器的完成来决议的!

详细完成见:

  • github.com/rust-lang/r…

Any基本运用

上一小节提到了 Any 可以完成:

  • 取得变量的类型TypeId;
  • 判别变量是否是指定类型;
  • 把any转化成指定类型;
  • 获取类型的姓名;

下面咱们经过详细代码来看:

examples/0_any.rs

use std::any::{Any, TypeId};
struct Person {
    pub name: String,
}
/// 获取TypeId
fn is_string(s: &dyn Any) -> bool {
    TypeId::of::<String>() == s.type_id()
}
/// 判别是否是指定类型
fn check_string(s: &dyn Any) {
    if s.is::<String>() {
        println!("It's a string!");
    } else {
        println!("Not a string...");
    }
}
/// 转化Any为特定类型
fn print_if_string(s: &dyn Any) {
    if let Some(ss) = s.downcast_ref::<String>() {
        println!("It's a string({}): '{}'", ss.len(), ss);
    } else {
        println!("Not a string...");
    }
}
/// 获取类型的姓名
/// 经过此函数取得的姓名不仅有!
/// 比方type_name::<Option<String>>()或许回来"Option<String>"或"std::option::Option<std::string::String>"
/// 一起编译器版别不同回来值或许不同
fn get_type_name<T>(_: &T) -> String {
    std::any::type_name::<T>().to_string()
}
fn main() {
    let p = Person { name: "John".to_string() };
    assert!(!is_string(&p));
    assert!(is_string(&p.name));
    check_string(&p);
    check_string(&p.name);
    print_if_string(&p);
    print_if_string(&p.name);
    println!("Type name of p: {}", get_type_name(&p));
    println!("Type name of p.name: {}", get_type_name(&p.name));
}

输出如下:

Not a string...
It's a string!
Not a string...
It's a string(4): 'John'
Type name of p: 0_any::Person
Type name of p.name: alloc::string::String

总结如下:

/// 获取TypeId,并比较: type_id
TypeId::of::<String>() == s.type_id()
/// 判别是否是指定类型: s.is
s.is::<String>()
/// 转化Any为特定类型: s.downcast_ref
s.downcast_ref::<String>()
/// 获取类型的姓名: type_name::<T>()
/// 经过此函数取得的姓名不仅有!
/// 比方type_name::<Option<String>>()或许回来"Option<String>""std::option::Option<std::string::String>"
/// 一起编译器版别不同回来值或许不同
std::any::type_name::<T>().to_string()

Any的运用场景

Rust 中的 Any 类似于 Java 中的 Object,可以传入任何拥有静态生命周期的类型;

因此在有些入参类型复杂的场景,咱们可以简化入参;

例如,打印任何类型对应的值:

examples/1_print_any.rs

use std::any::Any;
use std::fmt::Debug;
#[derive(Debug)]
struct MyType {
    name: String,
    age: u32,
}
fn print_any<T: Any + Debug>(value: &T) {
    let value_any = value as &dyn Any;
    if let Some(string) = value_any.downcast_ref::<String>() {
        println!("String ({}): {}", string.len(), string);
    } else if let Some(MyType { name, age }) = value_any.downcast_ref::<MyType>() {
        println!("MyType ({}, {})", name, age)
    } else {
        println!("{:?}", value)
    }
}
fn main() {
    let ty = MyType {
        name: "Rust".to_string(),
        age: 30,
    };
    let name = String::from("Rust");
    print_any(&ty);
    print_any(&name);
    print_any(&30);
}

如上所示,不论是 String 类型、MyType 自界说类型,仍是内置的i32类型,都可以被打印,只要他们完成了 Debug Trait;

可以以为这是Rust中一种函数重载的方式,在读取一些结构复杂的装备时,也可以直接运用Any;

总结

any特性并非实践意义上的 Reflection,最多是编译时反射;一起Rust仅仅启用类型查看和类型转化,而不是查看恣意结构的内容;

any符合零成本笼统,由于Rust只会针对调用相关函数的类型生成代码,并且判别类型时回来的是编译器内部的类型ID,没有额外的开支;甚至可以直接运用TypeId::of::<String>,然后没有了dyn any的动态绑定的开支;

虽然Rust没有供给 Reflection,可是进程宏可以完成大部分反射可以完成的功用!

实践上,在Rust的早期版别中供给了 Reflection功用,可是在14年移除了相关代码,原因是:

  • 反射打破了原有的封装准则,可以恣意访问结构体的内容,不安全
  • 反射的存在使得代码过于臃肿,移除后编译器会简化许多;
  • 反射功用设计的比较弱,开发者对于是否在未来的版别中还拥有反射功用存疑;

至于保留any的原因:

  • 在调试范型类型相关的代码的时候,有TypeId会更方便,更简单给出正确的错误提示;
  • 有利于编译器作出代码的优化;

附录

源码:

  • github.com/JasonkayZK/…

文章参阅:

  • www.coder.rs/index.php/a…
  • internals.rust-lang.org/t/pre-rfc-r…
  • rust.ffactory.org/std/any/ind…
  • www.coder.rs/index.php/a…
  • www.jianshu.com/p/c4ef17bb1…

文章转自:rustcc.cn/article?id=…