Ya he leído el término "puntero gordo" en varios contextos, pero no estoy seguro de qué significa exactamente y cuándo se usa en Rust. El puntero parece ser dos veces más grande que un puntero normal, pero no entiendo por qué. También parece tener algo que ver con los objetos de rasgo.
91
Respuestas:
El término "puntero gordo" se utiliza para referirse a referencias y punteros en bruto a tipos de tamaño dinámico (DST): cortes u objetos de rasgo. Un puntero grueso contiene un puntero más información que hace que el DST sea "completo" (por ejemplo, la longitud).
Los tipos más utilizados en Rust no son DST, pero tienen un tamaño fijo conocido en el momento de la compilación. Estos tipos implementan el
Sized
rasgo . Incluso los tipos que administran un búfer de pila de tamaño dinámico (comoVec<T>
) sonSized
como el compilador sabe el número exacto de bytesVec<T>
que ocupará una instancia en la pila. Actualmente, hay cuatro tipos diferentes de DST en Rust.Rebanadas (
[T]
ystr
)El tipo
[T]
(para cualquieraT
) tiene un tamaño dinámico (también lo es el tipo especial de "segmento de cadena"str
). Es por eso que normalmente solo lo ves como&[T]
o&mut [T]
, es decir, detrás de una referencia. Esta referencia es un "puntero gordo". Vamos a revisar:dbg!(size_of::<&u32>()); dbg!(size_of::<&[u32; 2]>()); dbg!(size_of::<&[u32]>());
Esto imprime (con algo de limpieza):
Entonces vemos que una referencia a un tipo normal como
u32
tiene un tamaño de 8 bytes, al igual que una referencia a una matriz[u32; 2]
. Esos dos tipos no son DST. Pero al igual[u32]
que un DST, la referencia es dos veces mayor. En el caso de las rebanadas, los datos adicionales que "completan" el DST son simplemente la longitud. Entonces se podría decir que la representación de&[u32]
es algo como esto:struct SliceRef { ptr: *const u32, len: usize, }
Objetos de rasgo (
dyn Trait
)Cuando se usan rasgos como objetos de rasgo (es decir, tipo borrado, despachado dinámicamente), estos objetos de rasgo son DST. Ejemplo:
trait Animal { fn speak(&self); } struct Cat; impl Animal for Cat { fn speak(&self) { println!("meow"); } } dbg!(size_of::<&Cat>()); dbg!(size_of::<&dyn Animal>());
Esto imprime (con algo de limpieza):
Nuevamente,
&Cat
solo tiene 8 bytes de tamaño porqueCat
es un tipo normal. Perodyn Animal
es un objeto de rasgo y, por lo tanto, de tamaño dinámico. Como tal,&dyn Animal
tiene un tamaño de 16 bytes.En el caso de los objetos de rasgo, los datos adicionales que completan el DST son un puntero a la vtable (vptr). No puedo explicar completamente el concepto de vtables y vptrs aquí, pero se utilizan para llamar a la implementación del método correcto en este contexto de despacho virtual. La vtable es un dato estático que básicamente solo contiene un puntero de función para cada método. Con eso, una referencia a un objeto de rasgo se representa básicamente como:
struct TraitObjectRef { data_ptr: *const (), vptr: *const (), }
(Esto es diferente de C ++, donde el vptr para clases abstractas se almacena dentro del objeto. Ambos enfoques tienen ventajas y desventajas).
DST personalizados
De hecho, es posible crear sus propias DST al tener una estructura donde el último campo es un DST. Sin embargo, esto es bastante raro. Un ejemplo destacado es
std::path::Path
.Una referencia o un puntero al horario de verano personalizado también es un puntero grueso. Los datos adicionales dependen del tipo de DST dentro de la estructura.
Excepción: tipos externos
En RFC 1861 ,
extern type
se introdujo la función. Los tipos externos también son DST, pero sus indicadores no son indicadores gordos. O más exactamente, como dice el RFC:Pero si no está interactuando con una interfaz C, probablemente nunca tendrá que lidiar con estos tipos externos.
Arriba, hemos visto los tamaños para referencias inmutables. Los punteros gordos funcionan de la misma manera para referencias mutables, punteros crudos inmutables y punteros crudos mutables:
size_of::<&[u32]>() = 16 size_of::<&mut [u32]>() = 16 size_of::<*const [u32]>() = 16 size_of::<*mut [u32]>() = 16
fuente