Transformationen magischer Datentypen in Rust: Intrinsika mem :: transmute


Einführung


Die Programmiersprache Rust verfügt trotz der umfassenden Ideologie der Datensicherheit auch über unsichere Programmiermethoden, da sie manchmal die Geschwindigkeit erhöhen können, indem unnötige Berechnungen vermieden werden, und manchmal ist dies einfach eine wichtige Notwendigkeit.


Eine davon ist unsere aktuelle Instanz - intrinsics mem::transmute<T, U> , die für beide ein wenig entwickelt wurde und sich in äußerst ungewöhnlichen Situationen als nützlich erweist.


Funktionsbeschreibung


mem::transmute<T, U> direkt vom Compiler implementiert, da es per Definition nicht durch die Syntax der Rust-Sprache beschrieben werden kann. Es interpretiert einfach die Bits eines Datentyps als die Bits eines anderen neu:


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

Vor der Neuinterpretation übernimmt diese Funktion den Besitz der neu interpretierten Variablen vom Datentyp T und „vergisst“ sie danach (ohne jedoch den entsprechenden Destruktor aufzurufen).


Im physischen Speicher findet kein Kopieren statt, da diese Eigenschaft dem Compiler nur klar macht, dass der Inhalt vom Typ T tatsächlich vom Typ U


Ein Kompilierungsfehler tritt auf, wenn die Typen T und U unterschiedlich lang sind. Weder das Argument noch der Rückgabewert können ungültige Werte sein .


Undefiniertes Verhalten


Wie Sie vielleicht bemerkt haben, wird diese Funktion als unsicher markiert, was logisch ist, da sie aufgrund vieler Faktoren undefiniertes Verhalten erzeugen kann:


  • Erstellen einer Instanz eines beliebigen Typs mit einem ungültigen Status;
  • Erstellen eines Grundelements mit einem ungültigen Wert;
  • Um die Typinferenz zu erfüllen, ist ein völlig unerwarteter Ausgabetyp möglich, wenn er nicht angegeben wird.
  • Konvertierungen zwischen non-repr(C) -Typen;
  • Die Konvertierung in eine reguläre Verknüpfung ohne explizit angegebene Lebensdauer führt zu einer nicht verwandten Lebensdauer .
  • Die Umwandlung eines unveränderlichen Links in einen veränderlichen.

Offene Möglichkeiten


Diese Technik rechtfertigt sich jedoch in einigen speziellen Fällen, z. B. beim Erhalten eines Bitmusters eines Gleitkomma-Datentyps (oder allgemeiner beim Typ Punning, bei dem T und U keine Rohzeiger sind):


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

Konvertieren Sie einen Rohzeiger in einen Zeiger auf eine Funktion. Es ist anzumerken, dass diese Technik nicht zwischen Plattformen portierbar ist, auf denen Funktionszeiger und reguläre Zeiger unterschiedlich groß sind.


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

Verlängerung der Lebenszeit oder Verkürzung der unveränderlichen Lebenszeit. Dies ist eine sehr unsichere und gleichzeitig erweiterte Rust-Syntax:


 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) } 

Sparsame Alternativen


Grundsätzlich kann as mühelos undefiniertes Verhalten verhindern, das durch 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) }; 

Es können auch sichere Methoden verwendet werden, die das Gleiche tun wie ein ähnlicher Aufruf von 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]); 

Und dieser Quellcode wird durch drei Implementierungen der Funktion slice::split_at_mut() demonstriert: Verwenden von mem::transmute<T, U> as Operatoren und der Funktion 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)) } } 

Mit anderen Worten, die Verwendung von mem::transmute<T, U> nur dann gerechtfertigt, wenn nichts anderes hilft (die Analogie zur Reflexion in einigen Sprachen ist hier angebracht).


Variation mem :: transmute_copy <T, U>


Das übliche intrinsische mem::transmute<T, U> möglicherweise nicht geeignet, wenn eine Übertragung des Eigentums an einer Variablen vom Typ T unmöglich ist. Seine Variation mem::transmute_copy<T, U> hilft:


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

Wie Sie vielleicht vermutet haben, wird anstelle eines einzelnen Arguments eine vollständige Kopie davon erstellt und das Eigentum an dem Ergebnis übertragen. Diese Variante arbeitet langsamer, daher wird empfohlen, sie seltener zu verwenden.


Im Gegensatz zu mem::transmute<T, U> das aktuelle Analogon keinen Kompilierungsfehler, wenn die Typen T und U unterschiedliche Längen in Bytes haben. Es wird jedoch dringend empfohlen, es nur aufzurufen, wenn sie dieselbe Größe haben.


Es ist auch zu beachten, dass diese Funktion ein undefiniertes Verhalten erzeugt, wenn eine Größe vom Typ U eine Größe von T überschreitet.


Fazit


Ich stelle noch einmal fest, dass die fraglichen Funktionen nur in den Fällen verwendet werden sollten, in denen Sie überhaupt nicht darauf verzichten können. Wie Nomicon sagt, ist dies wirklich das unsicherste, was Sie in Rust tun können.


Alle Beispiele aus diesem Artikel stammen aus der mem::transmute<T, U> , und es wurden auch Materialien von hier und hier verwendet. Ich hoffe, der Artikel hat Ihnen geholfen.

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


All Articles