Il termine "puntatore grasso" viene utilizzato per fare riferimento a riferimenti e puntatori non elaborati a tipi di dimensioni dinamiche (DST): sezioni o oggetti tratto. Un puntatore grasso contiene un puntatore più alcune informazioni che rendono il DST "completo" (ad esempio la lunghezza).
I tipi più comunemente usati in Rust non sono DST, ma hanno una dimensione fissa nota al momento della compilazione. Questi tipi implementano il Sized
tratto . Anche i tipi che gestiscono un buffer di heap di dimensioni dinamiche (come Vec<T>
) sono Sized
poiché il compilatore conosce il numero esatto di byte che Vec<T>
un'istanza occuperà nello stack. Attualmente ci sono quattro diversi tipi di DST in Rust.
Fette ( [T]
e str
)
Il tipo [T]
(per any T
) è dimensionato dinamicamente (così è il tipo speciale "string slice" str
). Ecco perché di solito lo vedi solo come &[T]
o &mut [T]
, cioè dietro un riferimento. Questo riferimento è un cosiddetto "fat pointer". Controlliamo:
dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());
Questo stampa (con un po 'di pulizia):
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
Quindi vediamo che un riferimento a un tipo normale come u32
è grande 8 byte, così come un riferimento a un array [u32; 2]
. Questi due tipi non sono DST. Ma poiché [u32]
è un DST, il riferimento ad esso è due volte più grande. Nel caso delle sezioni, i dati aggiuntivi che "completano" l'ora legale sono semplicemente la lunghezza. Quindi si potrebbe dire che la rappresentazione di &[u32]
è qualcosa del genere:
struct SliceRef {
ptr: *const u32,
len: usize,
}
Oggetti tratto ( dyn Trait
)
Quando si usano i tratti come oggetti tratto (cioè il tipo cancellato, inviato dinamicamente), questi oggetti tratto sono DST. Esempio:
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>());
Questo stampa (con un po 'di pulizia):
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
Anche in questo caso, &Cat
è grande solo 8 byte perché Cat
è un tipo normale. Ma dyn Animal
è un oggetto tratto e quindi dimensionato dinamicamente. In quanto tale, &dyn Animal
è grande 16 byte.
Nel caso di oggetti tratto, i dati aggiuntivi che completano il DST sono un puntatore a vtable (il vptr). Non posso spiegare completamente il concetto di vtables e vptrs qui, ma sono usati per chiamare l'implementazione corretta del metodo in questo contesto di invio virtuale. Il vtable è un pezzo di dati statico che fondamentalmente contiene solo un puntatore a funzione per ogni metodo. Con ciò, un riferimento a un oggetto tratto è fondamentalmente rappresentato come:
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(Questo è diverso da C ++, dove il vptr per le classi astratte è memorizzato all'interno dell'oggetto. Entrambi gli approcci hanno vantaggi e svantaggi.)
DST personalizzati
In realtà è possibile creare i propri DST avendo una struttura in cui l'ultimo campo è un DST. Questo è piuttosto raro, però. Un esempio importante è std::path::Path
.
Un riferimento o un puntatore all'ora legale personalizzata è anche un puntatore grasso. I dati aggiuntivi dipendono dal tipo di DST all'interno della struttura.
Eccezione: tipi esterni
In RFC 1861 , la extern type
funzionalità è stata introdotta. Anche i tipi esterni sono DST, ma i puntatori ad essi non sono puntatori fat. O più esattamente, come dice la RFC:
In Rust, i puntatori ai DST trasportano metadati sull'oggetto a cui si punta. Per stringhe e slice questa è la lunghezza del buffer, per gli oggetti trait questa è la vtable dell'oggetto. Per i tipi esterni i metadati sono semplicemente ()
. Ciò significa che un puntatore a un tipo esterno ha la stessa dimensione di a usize
(cioè non è un "puntatore grasso").
Ma se non stai interagendo con un'interfaccia C, probabilmente non dovrai mai avere a che fare con questi tipi esterni.
Sopra, abbiamo visto le dimensioni per i riferimenti immutabili. I puntatori grassi funzionano allo stesso modo per riferimenti mutabili, puntatori grezzi immutabili e puntatori grezzi mutabili:
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16