Rust 如何实现反射?

Rust 具有 Any特性,但它也有一个“不为你不使用的东西付费”的策略。Rust 如何实现反射?

我猜 Rust 用的是懒标签。每个类型最初都是未赋值的,但是如果类型的一个实例被传递给一个期望 Any特性的函数,该类型就会被赋值一个 TypeId

或者 Rust 在每个类型的实例可能传递给该函数的类型上都设置 TypeId?我想前者会很贵。

26011 次浏览

首先,Rust 没有反射; 反射意味着您可以在运行时获得关于类型的详细信息,比如字段、方法、它实现的接口,等等。不能使用 Rust 来实现这一点。最接近的方法是显式实现(或派生)提供此信息的 trait。

每种类型都会在编译时获得一个分配给它的 TypeId。因为具有全局排序的 ID 是 用力,所以 ID 是一个整数,它派生自类型定义和关于包含它的板条箱的分类元数据的组合。换句话说: 它们不是按照任何顺序分配的,它们只是用来定义类型的各种信息的 大麻。[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位整数。

Anyget_type_id转到这里,这意味着 get_type_id真的,只是将 trait 方法绑定到适当的 TypeId::of方法。它只是为了确保如果你有一个 Any,你可以找到原始类型的 TypeId

现在,Any是针对 大部分类型实现的,但这并不意味着所有这些类型的 实际上Any实现都漂浮在内存中。实际发生的情况是,编译器只有在 某人编写需要它的代码时才为类型的 Any实现生成实际代码。[3]换句话说,如果你从来没有为给定的类型使用 Any实现,编译器将永远不会生成它。

这就是 Rust 实现“不为你不使用的东西付费”的方式: 如果你从来没有传递给定的类型为 &AnyBox<Any>,那么相关的代码将永远不会生成,也不会占用你编译的二进制文件中的任何空间。


[1] : 令人沮丧的是,这意味着一个类型的 TypeId可以 改变价值取决于精确的 怎么做库得到编译,以至于编译它作为一个依赖项(而不是作为一个独立的构建)导致 TypeId发生变化。

[2] : 据我所知。关于这一点,我 可以是错误的,但是如果是这样的话,我会对 真的感到惊讶。

[3] : 这是 Rust 中泛型的 一般来说