Clause de non-responsabilité : Il n'y a aucune garantie que le comportement exact observé ici sera toujours vrai. VOUS NE DEVEZ PAS VOUS FIER AUX VALEURS SPÉCIFIQUES. De plus, cette réponse n'est basée que sur des observations et ne concerne que les enums ; d'autres optimisations de la mémoire existent dans les environs &T
s, NonNull<T>
s, NonZeroU8
(et famille), et les structures imbriquées qui les contiennent.
Enums de base
Si vous avez une énumération simple sans structure imbriquée, le comportement par défaut est que les variantes de l'énumération commencent à zéro et s'incrémentent vers le haut. Ainsi, le premier motif binaire non représentable est utilisé en tant que motif None
valeur :
enum Simple { A, B };
println!("{}", unsafe { transmute::<Option<Simple>, u8>(None) });
// prints 2
Si votre simple enum sans structure imbriquée laisse un vide à l'avant, la fonction None
sera toujours représentée par le premier motif binaire non représentable après les représentations de la variante enum :
enum GapInFront { A = 1, B };
println!("{}", unsafe { transmute::<Option<GapInFront>, u8>(None) });
// prints 3
Si vous laissez un espace au début et que vous avez une variante à la fin de l'espace binaire, ce n'est qu'à ce moment-là qu'il utilisera tous les zéros en tant qu'élément de l'espace binaire. None
valeur :
enum ExtendsToEnd { A = 1, B = 255 };
println!("{}", unsafe { transmute::<Option<ExtendsToEnd>, u8>(None) });
// prints 0
Une chose est à noter, c'est qu'il jamais choisir une représentation entre les variantes pour le None
valeur. Même s'il existe de nombreux motifs binaires non représentables, les variantes occupant les limites entraîneront l'utilisation de 2 octets :
enum Full { A = 0, B = 255 };
println!("{:?}", unsafe { transmute::<Option<Full>, [u8; 2]>(None) });
// prints [0, 60], which I believe is undefined behavior
// since I think the second byte is left uninitialized
Je pense que le compilateur ne garde pas trace de tous les motifs de bits représentables, et qu'il ne conserve que les plages permettant d'effectuer ces vérifications.
Plus d'informations intéressantes.
Si votre enum possède une variante avec une valeur d'enum imbriquée, cela sera également pris en compte :
enum Nested { A, B };
enum Complex { A(Nested), B };
println!("{}", unsafe { transmute::<Option<Complex>, u8>(None) });
// prints 3
Cependant, il semble se casser la figure si deux variantes ont des valeurs, même si leurs motifs de bits ne se chevauchent pas (triste) :
enum Nested1 { A, B };
enum Nested2 { A=2, B };
enum MoreComplex { A(Nested1), B(Nested2) };
println!("{:?}", unsafe { transmute::<Option<MoreComplex>, [u8; 2]>(None) });
// prints [2, 211], again the second byte is left uninitialized
Autre point à souligner, il ne s'agit pas d'un cas particulier avec Option
; si vous définissez votre propre type d'option, le comportement est le même :
enum MyOption<T> { None, Some(T) };
println!("{}", unsafe { transmute::<MyOption<Simple>, u8>(MyOption::None) });
// prints 2
Voir tout cela sur l'aire de jeux .
Voir aussi Quel est l'overhead du type Option de Rust ? .