
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) };
Dan metode aman juga dapat digunakan, melakukan hal yang sama dengan panggilan serupa untuk mem::transmute<T, U>
:
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};
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.