Transformations magiques des types de données dans Rust: Intrinsika mem :: transmute


Présentation


Le langage de programmation Rust , malgré l'idéologie complète de la sécurité des données, a également des méthodes de programmation dangereuses, car parfois elles peuvent augmenter la vitesse en éliminant les calculs inutiles, et parfois c'est juste une nécessité vitale.


L'un d'eux est notre instance actuelle - intrinsèques mem::transmute<T, U> , conçu pour les deux un peu, utile dans des situations extrêmement inhabituelles.


Description fonctionnelle


mem::transmute<T, U> implémenté directement par le compilateur, car par définition il ne peut pas être décrit par la syntaxe du langage Rust. Il réinterprète simplement les bits d'un type de données comme les bits d'un autre:


 pub unsafe extern "rust-intrinsic" fn transmute<T, U>(e: T) -> U 

Avant de réinterpréter, cette fonction prend en charge la propriété de la variable réinterprétée du type de données T , et ensuite elle "l'oublie" (mais sans appeler le destructeur correspondant).


Aucune copie n'a lieu dans la mémoire physique, car cet intrinsèque indique simplement au compilateur que le contenu de type T est en fait de type U


Une erreur de compilation se produira si les types T et U ont des longueurs différentes. Ni l'argument ni la valeur de retour ne peuvent être des valeurs non valides .


Comportement indéfini


Comme vous l'avez peut-être remarqué, cette fonction est marquée comme non sécurisée, ce qui est logique, car elle peut générer un comportement non défini en raison de nombreux facteurs:


  • Création d'une instance de tout type avec un état non valide;
  • Création d'une primitive avec une valeur non valide;
  • Afin de satisfaire l'inférence de type, un type de sortie complètement inattendu est possible s'il n'est pas spécifié;
  • Conversions entre types non-repr(C) ;
  • La conversion en un lien régulier sans durée de vie explicitement spécifiée entraîne une durée de vie sans rapport ;
  • La conversion d'un lien immuable en un lien mutable.

Opportunités ouvertes


Cependant, cette technique se justifie dans certains cas particuliers, par exemple, en obtenant un motif binaire d'un type de données à virgule flottante (ou, plus généralement, un découpage de type, où T et U ne sont pas des pointeurs bruts):


 let bitpattern = unsafe { std::mem::transmute::<f32, u32>(1.0) }; assert_eq!(bitpattern, 0x3F800000); 

Convertissez un pointeur brut en un pointeur vers une fonction. Il convient de noter que cette technique n'est pas portable entre les plates-formes où les pointeurs de fonction et les pointeurs réguliers varient en taille.


 fn foo() -> i32 { 0 } let pointer = foo as *const (); let function = unsafe { std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0); 

Extension ou raccourcissement de la durée de vie invariante. Il s'agit d'une syntaxe Rust très peu sûre et en même temps avancée:


 struct R<'a>(&'a i32); unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> { std::mem::transmute::<R<'b>, R<'static>>(r) } unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> { std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r) } 

Épargner des alternatives


Fondamentalement, as peut sans effort empêcher un comportement indéfini causé par mem::transmute<T, U> :


 let ptr = &mut 0; let val_transmuted = unsafe { std::mem::transmute::<&mut i32, &mut u32>(ptr) }; //   `as`  .  ,  //   `as    let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) }; 

Et des méthodes sûres peuvent également être utilisées, faisant la même chose qu'un appel similaire à mem::transmute<T, U> :


 //        //    let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") }; assert_eq!(slice, &[82, 117, 115, 116]); //      `as_bytes()`,  //     let slice = "Rust".as_bytes(); assert_eq!(slice, &[82, 117, 115, 116]); //      //   assert_eq!(b"Rust", &[82, 117, 115, 116]); 

Et ce code source est démontré par trois implémentations de la fonction slice::split_at_mut() : en utilisant mem::transmute<T, U> , as opérateurs et la fonction slice::from_raw_parts() .


 use std::{slice, mem}; //     ,    //     fn split_at_mut_transmute<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let slice2 = mem::transmute::<&mut [T], &mut [T]>(slice); // -, mem::transmute<T, U>   , //  ,    -    T  U. // // -,      ,  //        (&mut slice[0..mid], &mut slice2[mid..len]) } } //       ; `&mut *` //    `&mut T`  `&mut T`  `*mut T`. fn split_at_mut_casts<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let slice2 = &mut *(slice as *mut [T]); //        ,  //        (&mut slice[0..mid], &mut slice2[mid..len]) } } //    ,    //  fn split_at_stdlib<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let ptr = slice.as_mut_ptr(); //      ,  //    : `slice`, r-value ret.0  r-value ret.1. // // `slice`     `let ptr = ...`,   // ,    "",  ,  //      . (slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid)) } } 

En d'autres termes, l'utilisation de mem::transmute<T, U> justifiée que lorsque rien d'autre n'aide (l'analogie avec la réflexion dans certaines langues est appropriée ici).


Variation mem :: transmute_copy <T, U>


Les intrinsèques usuels mem::transmute<T, U> peuvent ne pas convenir dans les cas où le transfert de propriété d'une variable de type T impossible. Sa variation mem::transmute_copy<T, U> vient à la rescousse:


 pub unsafe fn transmute_copy<T, U>(src: &T) -> U 

Comme vous l'avez peut-être deviné, au lieu de déplacer un seul argument, il en fait une copie complète et transfère la propriété du résultat. Cette variation fonctionnera plus lentement, il est donc recommandé de l'utiliser moins souvent.


Contrairement à mem::transmute<T, U> , l'analogue actuel ne génère pas d'erreur de compilation si les types T et U ont des longueurs d'octets différentes, mais il est fortement recommandé de l'appeler uniquement s'ils ont la même taille.


Il convient également de rappeler que si une taille de type U dépasse une taille de T , cette fonction génère un comportement indéfini.


Conclusion


Encore une fois, je note que les fonctions en question ne doivent être utilisées que dans les cas où vous ne pouvez pas vous en passer. Comme le dit Nomicon , c'est vraiment la chose la plus précaire que vous puissiez faire à Rust.


Tous les exemples de cet article sont tirés de la mem::transmute<T, U> , et des matériaux d'ici et ici ont également été utilisés. J'espère que l'article vous a été utile.

Source: https://habr.com/ru/post/fr448240/


All Articles