Le terme "gros pointeur" est utilisé pour désigner des références et des pointeurs bruts vers des types dimensionnés dynamiquement (DST) - des tranches ou des objets de trait. Un gros pointeur contient un pointeur plus quelques informations qui rendent le DST "complet" (par exemple la longueur).
Les types les plus couramment utilisés dans Rust ne sont pas des DST, mais ont une taille fixe connue au moment de la compilation. Ces types implémentent le Sized
trait . Même les types qui gèrent un tampon de tas de taille dynamique (comme Vec<T>
) le sont, Sized
car le compilateur connaît le nombre exact d'octets qu'une Vec<T>
instance prendra sur la pile. Il existe actuellement quatre types différents de DST dans Rust.
Tranches ( [T]
et str
)
Le type [T]
(pour tout T
) est dimensionné dynamiquement (de même que le type spécial "string slice" str
). C'est pourquoi vous ne le voyez généralement que comme &[T]
ou &mut [T]
, c'est-à-dire derrière une référence. Cette référence est ce que l'on appelle un "gros pointeur". Allons vérifier:
dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());
Cela imprime (avec quelques nettoyages):
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
Nous voyons donc qu'une référence à un type normal comme u32
est de 8 octets de large, tout comme une référence à un tableau [u32; 2]
. Ces deux types ne sont pas des DST. Mais comme [u32]
c'est un DST, la référence à celui-ci est deux fois plus grande. Dans le cas des tranches, les données supplémentaires qui "complètent" le DST sont simplement la longueur. On pourrait donc dire que la représentation de &[u32]
est quelque chose comme ceci:
struct SliceRef {
ptr: *const u32,
len: usize,
}
Objets de trait ( dyn Trait
)
Lorsque vous utilisez des traits comme objets de trait (c'est-à-dire que le type est effacé, distribué dynamiquement), ces objets de trait sont des DST. Exemple:
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>());
Cela imprime (avec quelques nettoyages):
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
Encore une fois, il &Cat
ne fait que 8 octets car il Cat
s'agit d'un type normal. Mais dyn Animal
c'est un objet trait et donc dimensionné dynamiquement. En tant que tel, &dyn Animal
est de 16 octets.
Dans le cas des objets de trait, les données supplémentaires qui complètent le DST sont un pointeur vers la vtable (le vptr). Je ne peux pas expliquer complètement le concept de vtables et de vptrs ici, mais ils sont utilisés pour appeler l'implémentation de méthode correcte dans ce contexte de répartition virtuelle. La vtable est une donnée statique qui ne contient essentiellement qu'un pointeur de fonction pour chaque méthode. Avec cela, une référence à un objet trait est essentiellement représentée par:
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(Ceci est différent du C ++, où le vptr pour les classes abstraites est stocké dans l'objet. Les deux approches présentent des avantages et des inconvénients.)
DST personnalisés
Il est en fait possible de créer vos propres DST en ayant une structure où le dernier champ est un DST. C'est plutôt rare, cependant. Un exemple frappant est std::path::Path
.
Une référence ou un pointeur vers le DST personnalisé est également un gros pointeur. Les données supplémentaires dépendent du type de DST à l'intérieur de la structure.
Exception: types externes
Dans la RFC 1861 , la extern type
fonctionnalité a été introduite. Les types externes sont également des DST, mais les pointeurs vers eux ne sont pas des pointeurs gras. Ou plus exactement, comme le dit la RFC:
Dans Rust, les pointeurs vers les DST transportent des métadonnées sur l'objet pointé. Pour les chaînes et les tranches, il s'agit de la longueur du tampon, pour les objets de trait, c'est la vtable de l'objet. Pour les types externes, les métadonnées sont simples ()
. Cela signifie qu'un pointeur vers un type extern a la même taille qu'un usize
(c'est-à-dire qu'il ne s'agit pas d'un "gros pointeur").
Mais si vous n'interagissez pas avec une interface C, vous n'aurez probablement jamais à gérer ces types externes.
Ci-dessus, nous avons vu les tailles des références immuables. Les pointeurs Fat fonctionnent de la même manière pour les références mutables, les pointeurs bruts immuables et les pointeurs bruts mutables:
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16