reflection - ¿Cómo implementa Rust la reflexión?
(1)
En primer lugar, Rust no tiene reflejo; la reflexión implica que puede obtener detalles sobre un tipo en tiempo de ejecución, como los campos, métodos, interfaces que implementa, etc. No puede hacer esto con Rust. Lo más cercano que puede obtener es implementar explícitamente (o derivar) un rasgo que proporciona esta información.
Cada tipo obtiene un TypeId
asignado a él en tiempo de compilación. Debido a que tener identificadores ordenados globalmente es difícil , la ID es un entero derivado de una combinación de la definición del tipo y metadatos surtidos sobre la caja en la que está contenida. Para decirlo de otra manera: no están asignados en ningún tipo de orden, solo son hash de los diversos bits de información que entran en la definición del tipo. [1]
Si observa la fuente de Any
rasgo , verá la implementación única para Any
:
impl<T: ''static + ?Sized > Any for T {
fn get_type_id(&self) -> TypeId { TypeId::of::<T>() }
}
(Los límites se pueden reducir informalmente a "todos los tipos que no son prestados de otra cosa").
También puede encontrar la definición de 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
es una función interna reconocida por el compilador que, dado un tipo, devuelve su ID de tipo interno. Esta llamada simplemente se reemplaza en tiempo de compilación con el ID de tipo entero literal; no hay una llamada real aquí. [2] Así es como TypeId
sabe qué es la identificación de un tipo. TypeId
, entonces, es solo un envoltorio alrededor de este u64
para ocultar los detalles de implementación de los usuarios. Si lo encuentra conceptualmente más simple, puede pensar en TypeId
de un tipo como un entero constante de 64 bits que el compilador conoce en tiempo de compilación.
Any
get_type_id
a esto de get_type_id
, lo que significa que get_type_id
realmente solo está vinculando el método de rasgo con el método TypeId::of
apropiado. Solo está ahí para garantizar que, si tiene una Any
, puede averiguar TypeId
del tipo original.
Ahora, Any
se implementa para la mayoría de los tipos, pero esto no significa que todos esos tipos realmente tengan una implementación Any
flotando en la memoria. Lo que realmente ocurre es que el compilador solo genera el código real para la implementación Any
un tipo si alguien escribe el código que lo requiere. [3] En otras palabras, si nunca usa la implementación Any
para un tipo dado, el compilador nunca la generará.
Así es como Rust cumple "no pagues por lo que no usas": si nunca pasas un tipo dado como &Any
o Box<Any>
, entonces el código asociado nunca se genera y nunca ocupa espacio en tu binario compilado.
[1]: frustrantemente, esto significa que TypeId
un tipo puede cambiar el valor dependiendo de cómo se compila la biblioteca, hasta el punto de que compilarlo como una dependencia (en lugar de como una compilación independiente) hace que TypeId
s cambie.
[2]: En la medida en que soy consciente. Podría estar equivocado sobre esto, pero estaría realmente sorprendido si ese es el caso.
[3]: Esto es generalmente cierto de los genéricos en Rust.
Rust tiene el rasgo Any
, pero también tiene una política de "no pagues por lo que no usas". ¿Cómo implementa Rust la reflexión?
Mi suposición es que Rust usa etiquetado perezoso. Todos los tipos no están asignados inicialmente, pero más adelante si se pasa una instancia del tipo a una función que espera un rasgo Any
, al tipo se le asigna un TypeId
.
¿O quizás Rust pone un TypeId
en cada tipo que su instancia posiblemente se pasa a esa función? Supongo que lo primero sería caro.