Rust 具有
Any
的特点,但它也有“不为不使用的东西付费”的政策。 Rust是如何实现反射的?
我的猜测是 Rust 使用惰性标记。每个类型最初都是未分配的,但后来如果该类型的实例被传递给需要
Any
特征的函数,则该类型会被分配一个 TypeId
。
或者 Rust 可能会在其实例可能传递给该函数的每个类型上放置一个
TypeId
?我猜前者会很贵。
首先,Rust 没有反射;反射意味着您可以在运行时获取有关类型的详细信息,例如它实现的字段、方法、接口等。您不能使用 Rust 执行此操作。您可以获得的最接近的是显式实现(或派生)提供此信息的特征。
每种类型都会在编译时分配一个TypeId
。因为拥有全局有序的 ID 是“困难”的,所以 ID 是一个从类型定义和有关它所包含的 crate 的各种元数据的组合派生的整数。换句话说:它们没有以任何顺序分配,它们只是定义类型的各种信息的“散列”。 [1]如果您查看
Any
特征的 源代码,您将看到 Any
的单一实现:
impl<T: 'static + ?Sized > Any for T {
fn get_type_id(&self) -> TypeId { TypeId::of::<T>() }
}
(边界可以非正式地简化为“所有不是从其他东西借用的类型”。)
您还可以找到
TypeId
的定义:
pub struct TypeId {
t: u64,
}
impl TypeId {
pub const fn of<T: ?Sized + 'static>() -> TypeId {
TypeId {
t: unsafe { intrinsics::type_id::<T>() },
}
}
}
intrinsics::type_id
是编译器识别的内部函数,给定类型,返回其内部类型 ID。这个调用只是在编译时被替换为文字整数类型 ID;这里没有“实际”调用。 [2] 这就是 TypeId
知道类型 ID 的方式。那么,TypeId
只是这个
u64
的包装,以向用户隐藏实现细节。如果您发现它在概念上更简单,您可以将类型的 TypeId
视为编译器在编译时知道
的常量 64 位整数。
Any
从
get_type_id
转发到此,这意味着get_type_id
是真的
只是将特征方法绑定到适当的
TypeId::of
方法。它只是为了确保如果您有
Any
,您可以找到原始类型的
TypeId
。
现在,
Any
已为大多数
类型实现,但这并不意味着所有这些类型实际上都有一个
Any
实现在内存中浮动。实际发生的情况是,如果someone 编写了需要它的代码,编译器只会为类型的
Any
实现生成实际代码。 [3] 换句话说,如果您从不使用给定类型的 Any
实现,编译器将永远不会生成它。
这就是 Rust 实现“不为你不使用的东西付费”的方式:如果你从未将给定类型传递为
&Any
或 Box<Any>
,那么相关代码永远不会生成,也永远不会占用编译后的任何空间二进制。
[1]:令人沮丧的是,这意味着类型的
TypeId
可以更改值,具体取决于库的编译方式,以至于将其编译为依赖项(而不是作为独立构建)导致
TypeId
要改变。
[2]:据我所知。我可能错了,但如果真是这样,我会真的感到惊讶。
[3]:对于 Rust 中的泛型来说,这通常是正确的。
有一项倡议在 Rust 中实施反射,当前的 crate 是一个概念验证项目。
您可以检查一下,看看是否符合您的需求。
https://docs.rs/crate/reflect/latest