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 (como Vec<T>
) son Sized
como el compilador sabe el número exacto de bytes Vec<T>
que ocupará una instancia en la pila. Actualmente, hay cuatro tipos diferentes de DST en Rust.
Rebanadas ( [T]
y str
)
El tipo [T]
(para cualquiera T
) 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):
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
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):
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
Nuevamente, &Cat
solo tiene 8 bytes de tamaño porque Cat
es un tipo normal. Pero dyn 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:
En Rust, los punteros a las DST llevan metadatos sobre el objeto al que se apunta. Para cadenas y rebanadas, esta es la longitud del búfer, para objetos de rasgo, es la vtable del objeto. Para tipos externos, los metadatos son simples ()
. Esto significa que un puntero a un tipo externo tiene el mismo tamaño que un usize
(es decir, no es un "puntero gordo").
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