Transformasi ajaib tipe data di Rust: Intrinsika mem :: transmute


Pendahuluan


Bahasa pemrograman Rust , terlepas dari ideologi komprehensif keamanan data, juga memiliki metode pemrograman yang tidak aman, karena kadang-kadang mereka dapat meningkatkan kecepatan dengan menghilangkan perhitungan yang tidak perlu, dan kadang-kadang itu hanya kebutuhan vital.


Salah satunya adalah instance kita saat ini - intrinsik mem::transmute<T, U> , dirancang untuk mereka berdua sedikit, berguna dalam situasi yang sangat tidak biasa.


Deskripsi Fungsional


mem::transmute<T, U> diimplementasikan langsung oleh kompiler, karena menurut definisi itu tidak dapat dijelaskan oleh sintaks bahasa Rust. Itu hanya menafsirkan ulang bit dari satu tipe data sebagai bit yang lain:


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

Sebelum reinterpretasi, fungsi ini mengambil alih kepemilikan variabel yang ditafsirkan ulang tipe data T , dan setelah itu "melupakan" itu (tetapi tanpa memanggil destruktor yang sesuai).


Tidak ada penyalinan yang terjadi dalam memori fisik, karena intrinsik ini hanya menjelaskan kepada kompiler bahwa isi dari tipe T , pada kenyataannya, tipe U


Kesalahan kompilasi akan terjadi jika tipe T dan U memiliki panjang yang berbeda. Baik argumen maupun nilai kembalinya bisa menjadi nilai yang tidak valid .


Perilaku tidak terdefinisi


Seperti yang mungkin Anda perhatikan, fungsi ini ditandai sebagai tidak aman, yang logis, karena dapat menghasilkan perilaku tidak terdefinisi karena banyak faktor:


  • Membuat instance dari jenis apa pun dengan keadaan tidak valid;
  • Membuat primitif dengan nilai tidak valid;
  • Untuk memenuhi inferensi tipe, tipe output yang benar-benar tidak terduga dimungkinkan jika tidak ditentukan;
  • Konversi antara jenis non-repr(C) ;
  • Konversi ke tautan reguler tanpa waktu hidup yang ditentukan secara eksplisit menghasilkan waktu hidup yang tidak terkait ;
  • Konversi dari tautan yang tidak dapat diubah ke yang dapat diubah.

Peluang terbuka


Namun, teknik ini membenarkan dirinya dalam beberapa kasus khusus, misalnya, mendapatkan pola bit dari tipe data floating-point (atau, lebih umum, jenis punning, di mana T dan U bukan pointer mentah):


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

Ubah pointer mentah ke pointer menjadi fungsi. Perlu dicatat bahwa teknik ini tidak portabel antara platform di mana fungsi pointer dan pointer biasa bervariasi dalam ukuran.


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

Perpanjangan waktu hidup atau pemendekan waktu hidup invarian. Ini adalah sintaksis Rust yang sangat tidak aman dan pada saat yang sama:


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

Alternatif hemat


Pada dasarnya, as dapat dengan mudah mencegah perilaku tidak terdefinisi yang disebabkan oleh 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) }; 

Dan metode aman juga dapat digunakan, melakukan hal yang sama dengan panggilan serupa untuk 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]); 

Dan kode sumber ini ditunjukkan oleh tiga implementasi dari fungsi slice::split_at_mut() : using mem::transmute<T, U> , as operator dan fungsi 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)) } } 

Dengan kata lain, penggunaan mem::transmute<T, U> dibenarkan hanya jika tidak ada yang membantu (analogi dengan refleksi dalam beberapa bahasa sesuai di sini).


Variasi mem :: transmute_copy <T, U>


mem::transmute<T, U> intrinsik biasa mem::transmute<T, U> mungkin tidak cocok dalam kasus-kasus di mana pemindahan kepemilikan variabel tipe T mungkin. Variasi mem::transmute_copy<T, U> datang untuk menyelamatkan:


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

Seperti yang mungkin sudah Anda duga, alih-alih memindahkan satu argumen, argumen itu malah membuat salinan lengkap dan mengalihkan kepemilikan hasilnya. Variasi ini akan bekerja lebih lambat, jadi disarankan untuk menggunakannya lebih jarang.


Tidak seperti mem::transmute<T, U> , analog saat ini tidak menghasilkan kesalahan kompilasi jika tipe T dan U memiliki panjang byte yang berbeda, tetapi sangat disarankan untuk memanggilnya hanya jika mereka memiliki ukuran yang sama.


Perlu juga diingat bahwa jika ukuran tipe U melebihi ukuran T , maka fungsi ini menghasilkan perilaku yang tidak terdefinisi.


Kesimpulan


Sekali lagi, saya perhatikan bahwa fungsi yang dipermasalahkan harus digunakan hanya dalam kasus-kasus ketika Anda tidak bisa melakukannya tanpa mereka sama sekali. Seperti yang dikatakan Nomicon , ini benar-benar hal paling tidak aman yang dapat Anda lakukan di Rust.


Semua contoh dari artikel ini diambil dari mem::transmute<T, U> , dan bahan-bahan dari sini dan di sini juga digunakan. Semoga artikel ini bermanfaat bagi Anda.

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


All Articles