Unsafe Rust
Semua jaminan yang dibahas di kursus ini, seperti tidak ada referensi yang menggantung, tidak ada data race, dan tidak ada akses di luar batas, dipastikan oleh compiler. unsafe adalah caramu berkata ke compiler: “biarkan aku melakukan sesuatu yang tidak bisa kamu pastikan aman; aku yang bertanggung jawab.”
Apa yang dilakukan unsafe, dan apa yang tidak
Section titled “Apa yang dilakukan unsafe, dan apa yang tidak”Banyak orang mengira unsafe mematikan seluruh pemeriksaan keamanan Rust. Tidak begitu. Di dalam blok unsafe, kamu bisa melakukan tepat lima hal tambahan yang biasanya dilarang:
- Melakukan dereference pada raw pointer
- Memanggil fungsi
unsafe - Membaca atau mengubah variabel
staticyang mutable - Mengimplementasikan trait
unsafe - Mengakses field dari sebuah
union
Semua hal lain, seperti aturan borrowing, semantik move, dan pemeriksaan tipe, tetap berlaku penuh di dalam kode unsafe. unsafe itu pisau bedah untuk lima operasi tertentu, bukan saklar untuk mematikan semuanya.
Raw pointer
Section titled “Raw pointer”Referensi biasa di Rust (& dan &mut) selalu menunjuk ke data yang valid dan tersusun dengan benar di memori. Itu bagian dari jaminan compiler. Raw pointer (pointer mentah, *const T atau *mut T) melepaskan jaminan itu, dan karena itulah membacanya butuh unsafe:
fn main() { let value = 5; let pointer = &value as *const i32;
unsafe { println!("{}", *pointer); }}Output:
5Kamu boleh membuat raw pointer di kode yang aman. Yang butuh unsafe hanyalah dereference-nya (membaca nilai yang ditunjuknya), karena compiler tidak bisa lagi memastikan pointer itu valid.
Memanggil fungsi unsafe
Section titled “Memanggil fungsi unsafe”Beberapa fungsi ditandai unsafe karena punya syarat yang tidak bisa diperiksa compiler untukmu. Memanggilnya berarti kamu berjanji sudah memeriksa syarat-syarat itu sendiri:
unsafe fn dangerous() { println!("fungsi ini percaya kamu sudah memeriksa syarat-syaratnya");}
fn main() { unsafe { dangerous(); }}Output:
fungsi ini percaya kamu sudah memeriksa syarat-syaratnyaMembungkus unsafe di dalam fungsi yang aman
Section titled “Membungkus unsafe di dalam fungsi yang aman”Standard library punya fungsi unsafe sungguhan yang bisa kamu panggil. Salah satunya adalah get_unchecked pada slice. Cara kerjanya seperti numbers[i], tapi tanpa pemeriksaan batas. Kalau i di luar jangkauan, ia tidak panic. Ia membaca apa pun yang kebetulan ada di memori itu, dan ini disebut undefined behavior (perilaku yang tidak terdefinisi): programmu bisa crash, mencetak sampah, atau tampak baik-baik saja sampai suatu saat tidak lagi.
Jadi aturan untuk memanggilnya adalah “hanya dengan indeks yang kamu tahu valid”. Pola yang biasa dipakai adalah memeriksa aturan itu sendiri, di dalam fungsi biasa yang aman:
fn first_or_zero(numbers: &[i32]) -> i32 { if numbers.is_empty() { return 0; } // SAFETY: slice-nya tidak kosong, jadi indeks 0 pasti ada. unsafe { *numbers.get_unchecked(0) }}
fn main() { println!("{}", first_or_zero(&[7, 8, 9])); println!("{}", first_or_zero(&[]));}Output:
70Lihat apa yang dilihat pemanggilnya: fn first_or_zero(numbers: &[i32]) -> i32. Tidak ada unsafe di signature-nya, dan tidak ada cara untuk memanggilnya dengan salah, karena fungsinya memeriksa aturan sebelum sampai ke blok unsafe. Bagian unsafe-nya tetap kecil dan tersembunyi di dalam.
Ada dua kebiasaan yang diikuti developer Rust berpengalaman:
- Tulis komentar
// SAFETY:di atas setiap blokunsafe, yang menjelaskan kenapa aturannya terpenuhi. Komentar itu untuk orang berikutnya yang membaca kode, dan untuk dirimu sendiri di masa depan. - Buat blok
unsafesekecil mungkin, supaya kode yang perlu diperiksa ulang lebih sedikit.
Jujur saja, kamu tidak akan menulis fungsi persis seperti ini di kode sungguhan: numbers.first() melakukan hal yang sama dengan aman, dan compiler sangat pandai membuang pemeriksaan batas yang terbukti tidak diperlukan. Tapi polanya (periksa aturannya, lalu panggil unsafe di dalam fungsi yang aman) persis seperti cara Vec, String dan kawan-kawannya dibangun. Halaman FFI memakai trik yang sama untuk membungkus fungsi C.
Kenapa unsafe ada
Section titled “Kenapa unsafe ada”Kalau Rust begitu mementingkan keamanan, kenapa hal ini diizinkan? Karena ada hal-hal yang memang mustahil dibuktikan aman saat compile, padahal dalam praktiknya benar-benar aman: berkomunikasi langsung dengan hardware, memanggil library C (lihat FFI), atau membangun struktur data tertentu yang butuh performa tinggi. unsafe memungkinkan standard library (dan, sesekali, kodemu sendiri) mengimplementasikan bagian-bagian tingkat rendah ini, sementara semua yang dibangun di atasnya tetap berupa Rust biasa yang aman dan diperiksa penuh.
Kompromi yang jujur
Section titled “Kompromi yang jujur”Menulis unsafe bukan berarti kodemu sekarang pasti penuh bug. Artinya, compiler tidak bisa lagi menangkap jenis-jenis kesalahan tertentu untukmu di blok itu, dan kamulah yang harus menjaga jaminan yang sama secara manual. Kebanyakan developer Rust berpengalaman sangat jarang menulis kode unsafe secara langsung. Biasanya kode itu berada di bagian-bagian kecil yang ditinjau dengan cermat di lapisan paling bawah sebuah library, dibungkus dengan API yang aman untuk dipakai semua orang.
Latihan
Section titled “Latihan”1. Mengubah nilai lewat raw pointer
Section titled “1. Mengubah nilai lewat raw pointer”Buat let mut score = 10;, buat raw pointer *mut i32 ke variabel itu, lalu pakai pointer-nya untuk menambah 5. Setelah itu cetak score. Petunjuk: buat pointer-nya dengan &mut score as *mut i32, dan tulis *pointer += 5 di dalam blok unsafe.
Lihat solusi
fn main() { let mut score = 10; let pointer = &mut score as *mut i32; // membuatnya aman
unsafe { *pointer += 5; // menulis lewat pointer itu tidak aman }
println!("score = {score}");}Output:
score = 15Dalam praktiknya ini aman, karena score masih hidup dan tidak ada yang lain memakainya selagi kita menulis. Menepati janji-janji itu adalah tugasmu begitu kamu memakai unsafe.
2. get_or yang aman
Section titled “2. get_or yang aman”Tulis fn get_or(numbers: &[i32], index: usize, default: i32) -> i32. Fungsi ini mengembalikan elemen di posisi index, atau default kalau indeksnya di luar jangkauan. Pakai get_unchecked untuk membacanya, dan pastikan fungsinya tidak pernah bisa dipanggil dengan cara yang melanggar aturannya. Petunjuk: bandingkan index < numbers.len() dulu, dan hanya panggil get_unchecked kalau hasilnya true.
Lihat solusi
fn get_or(numbers: &[i32], index: usize, default: i32) -> i32 { if index < numbers.len() { // SAFETY: kita baru saja memeriksa bahwa index ada di dalam batas. unsafe { *numbers.get_unchecked(index) } } else { default }}
fn main() { let numbers = [10, 20, 30]; println!("{}", get_or(&numbers, 1, -1)); println!("{}", get_or(&numbers, 5, -1));}Output:
20-1Indeks 5 tidak pernah sampai ke blok unsafe, jadi ditangani dengan aman. Sebagai perbandingan, versi yang sepenuhnya aman adalah numbers.get(index).copied().unwrap_or(default), dan biasanya itulah yang akan kamu tulis.
Ringkasan
Section titled “Ringkasan”unsafemembuka tepat lima operasi (terutama raw pointer dan memanggil fungsi unsafe); semua hal lain tetap diperiksa penuh.- Raw pointer (
*const T/*mut T) boleh dibuat di kode yang aman; dereference-nya butuhunsafe. - Bungkus
unsafedi dalam fungsi kecil yang aman, yang memeriksa aturannya dulu, dan jelaskan kenapa ia benar di komentar// SAFETY:. unsafeada supaya kode tingkat rendah (akses hardware, interop dengan C) bisa dibangun, sementara kode yang memakainya tetap aman.- Sebagai pemula, kamu jarang perlu menulisnya sendiri. Cari abstraksi yang aman lebih dulu.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Apa yang sebenarnya dibuka oleh blok unsafe?
2. Bolehkah kamu membuat raw pointer di kode yang aman?
3. Sebagai pemula, seberapa sering kamu akan menulis unsafe sendiri?
4. Apakah menulis kode unsafe berarti programmu sekarang punya bug?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Unsafe Rust — The Rust Book (bahasa Inggris): kelima “unsafe superpowers” secara rinci, termasuk membangun
split_at_mutyang aman di atas raw pointer. - The Rustonomicon (bahasa Inggris): panduan resmi untuk menulis kode unsafe dengan benar. Tingkat lanjut, tapi bab pertamanya sangat layak dibaca.
- Kata kunci
unsafe— dokumentasi standard library (bahasa Inggris): blok, fungsi, dan trait unsafe, plus konvensi komentar// SAFETY:. - Unsafety — The Rust Reference (bahasa Inggris): daftar persis operasi yang butuh
unsafe. slice::get_unchecked— dokumentasi standard library (bahasa Inggris): fungsi unsafe sungguhan, dengan bagian “Safety” yang menjabarkan aturan yang harus kamu ikuti.