Chapter sebelumnya memetakan lima kemampuan yang dibuka unsafe, mulai dari dereferensi raw pointer hingga pemanggilan fungsi extern. Sekarang kita masuk ke pertanyaan praktis: di mana dan dengan bentuk apa kelima operasi itu boleh dieksekusi? Rust menyediakan dua kendaraan — blok unsafe yang menandai wilayah kecil di dalam fungsi biasa, dan fungsi unsafe yang menandai kontrak berisiko pada signature. Pilihan di antara keduanya menentukan siapa yang menanggung risiko: blok memusatkan tanggung jawab pada pemanggil di titik operasi, sedangkan fungsi unsafe mengumumkan risiko ke seluruh pengguna API. Chapter ini membedah kedua bentuk tersebut beserta aturan pembedahan kode yang menyertainya.

Blok unsafe: Pagar Terkecil #

Blok unsafe adalah pasangan kurung kurawal yang dibuka kata kunci unsafe. Di dalamnya, lima operasi berisiko menjadi legal; di luarnya, semuanya ditolak kompilator. Bentuk paling umum ditemui pada fungsi biasa yang bekerja dengan raw pointer:

rust
fn reset_saldo(saldo: *mut i64) {
    unsafe {
        *saldo = 0;
    }
}

fn main() {
    let mut saldo_koperasi: i64 = 450000;

    println!("Saldo sebelum reset: {}", saldo_koperasi);
    reset_saldo(&mut saldo_koperasi);
    println!("Saldo setelah reset: {}", saldo_koperasi);
}

Output yang Dihasilkan:

text
Saldo sebelum reset: 450000
Saldo setelah reset: 0

Mari kita bedah kode di atas baris per baris:

  1. Baris 1 (fn reset_saldo(saldo: *mut i64) {): signature fungsi sepenuhnya aman — parameter bertipe raw pointer tidak menuntut unsafe karena sekadar memegang pointer bukanlah operasi berisiko. Konsekuensinya, siapa pun boleh memanggil fungsi ini tanpa pagar tambahan.
  2. Baris 2 (unsafe {): pagar dibuka hanya satu operasi sebelum penutup. Wilayah sekecil ini mudah diaudit manusia: satu baris, satu risiko, satu tanggung jawab.
  3. Baris 3 (*saldo = 0;): dereferensi *saldo menulis angka 0 ke alamat yang ditunjuk pointer. Kompilator tidak memeriksa apakah saldo valid, selaras, atau terinisialisasi — fungsi ini percaya pemanggilnya menjaga ketiganya.
  4. Baris 11 (reset_saldo(&mut saldo_koperasi);): pemanggil membuat raw pointer *mut i64 dari &mut saldo_koperasi lewat konversi otomatis pada argumen. Karena pointer lahir dari variabel yang masih hidup, kontrak fungsi terpenuhi dan hasilnya terprediksi.
  5. Baris 12 (println!("Saldo setelah reset: {}", saldo_koperasi);): nilai asli terbaca 0 — bukti penulisan lewat pointer mengenai memori milik saldo_koperasi.

Blok unsafe juga adalah ekspresi biasa yang mengembalikan nilai, sehingga pola let x = unsafe { ... }; sah dipakai untuk menangkap hasil operasi berisiko dalam satu baris. Kita akan sering memakai bentuk ringkas ini di Chapter Static Mutable.

Fungsi unsafe: Kontrak pada Signature #

Kadang risiko melekat pada fungsi itu sendiri, bukan pada satu titik di tubuhnya. Kata kunci unsafe di depan fn mengubah fungsi menjadi unsafe function: tubuhnya boleh berisi operasi unsafe, dan — ini yang terpenting — setiap pemanggil wajib membungkus pemanggilannya dengan blok unsafe. Mari kita lihat apa yang terjadi bila pemanggil lupa:

rust
unsafe fn baca_sensor(alamat: *const u8) -> u8 {
    *alamat
}

fn main() {
    let alamat_sensor: *const u8 = std::ptr::null();
    let nilai = baca_sensor(alamat_sensor);
    println!("Nilai sensor: {}", nilai);
}

Kompilasi gagal dengan pesan asli berikut:

text
error[E0133]: call to unsafe function `baca_sensor` is unsafe and requires unsafe function or block
 --> main.rs:7:17
  |
7 |     let nilai = baca_sensor(alamat_sensor);
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
  |
  = note: consult the function's documentation for information on how to avoid undefined behavior

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0133`.

Error E0133 menyebutkan nama fungsinya secara eksplisit dan menuntut blok unsafe di sisi pemanggil. Pesan ini muncul justru karena tubuh baca_sensor belum dijalankan sama sekali — penolakan terjadi di gate kompilasi, bukan saat runtime. Catatan “consult the function’s documentation” juga bukan basa-basi: fungsi unsafe yang baik selalu mendokumentasikan kontraknya, seperti yang akan kita lakukan sebentar lagi.

Perhatikan satu ketidakadilan yang disengaja di contoh di atas: alamat_sensor bernilai null. Bila pemanggilan ini lolos (misalnya kita membungkusnya dengan unsafe begitu saja), dereferensi alamat null memicu undefined behavior. Keyword unsafe tidak menyelamatkan kita dari pointer buruk — keyword hanya memindahkan kewajiban memeriksanya.

Tubuh Fungsi unsafe adalah Konteks unsafe #

Pada edisi 2021 yang dipakai Course ini, tubuh sebuah fungsi unsafe otomatis menjadi konteks unsafe. Operasi berisiko di dalamnya boleh ditulis langsung tanpa blok bersarang:

rust
unsafe fn tulis_byte(buffer: *mut u8, nilai: u8) {
    *buffer = nilai;
}

fn main() {
    let mut sel: u8 = 0;
    unsafe {
        tulis_byte(&mut sel, 0x41);
    }
    println!("Sel berisi: 0x{:02X}", sel);
}

Output yang Dihasilkan:

text
Sel berisi: 0x41

Mari kita bedah kode di atas baris per baris:

  1. Baris 1 (unsafe fn tulis_byte(buffer: *mut u8, nilai: u8) {): penanda unsafe pada fungsi mengumumkan kontrak berisiko ke seluruh calon pemanggil.
  2. Baris 2 (*buffer = nilai;): dereferensi berjalan tanpa blok tambahan karena tubuh fungsi unsafe sudah menjadi konteks unsafe di edisi 2021.
  3. Baris 7 (unsafe {): pemanggil tetap wajib membuka blok unsafe — penanda pada definisi tidak meloloskan pemanggilan.
  4. Baris 8 (tulis_byte(&mut sel, 0x41);): raw pointer *mut u8 lahir dari &mut sel, buffer valid dan terinisialisasi, sehingga kontrak fungsi terpenuhi dan sel berisi 0x41.

Dokumentasikan Kontrak dengan # Safety #

Fungsi unsafe tanpa dokumentasi adalah bom waktu: pemanggil tidak tahu invariant apa yang harus dijaga. Konvensi komunitas Rust menuntut bagian # Safety pada doc comment yang menjelaskan syarat pemanggilan aman. Bentuk lengkapnya terlihat seperti ini:

rust
/// Membaca byte pada `indeks` dari buffer mentah.
///
/// # Safety
///
/// Pemanggil harus memastikan `buffer` menunjuk ke minimal `indeks + 1`
/// byte yang valid dan sudah terinisialisasi.
unsafe fn baca_byte(buffer: *const u8, indeks: usize) -> u8 {
    *buffer.add(indeks)
}

fn baca_byte_aman(buffer: &[u8], indeks: usize) -> Option<u8> {
    if indeks < buffer.len() {
        Some(unsafe { baca_byte(buffer.as_ptr(), indeks) })
    } else {
        None
    }
}

fn main() {
    let data_paket = [0x11, 0x52, 0x7A, 0x90];

    match baca_byte_aman(&data_paket, 2) {
        Some(nilai) => println!("Byte ke-2 paket: 0x{:02X}", nilai),
        None => println!("Indeks di luar jangkauan"),
    }

    match baca_byte_aman(&data_paket, 9) {
        Some(nilai) => println!("Byte ke-9 paket: 0x{:02X}", nilai),
        None => println!("Indeks 9 di luar jangkauan"),
    }
}

Output yang Dihasilkan:

text
Byte ke-2 paket: 0x7A
Indeks 9 di luar jangkauan

Mari kita bedah kode di atas baris per baris:

  1. Baris 7 (unsafe fn baca_byte(buffer: *const u8, indeks: usize) -> u8 {): fungsi berisiko diberi nama, parameter, dan kontrak yang jelas — bukan blok gelap yang tersebar di kode.
  2. Baris 8 (*buffer.add(indeks)): buffer.add(indeks) menggeser pointer sejauh indeks elemen, lalu dereferensi membaca byte pada alamat hasil. Kompilator tidak membuktikan bahwa offset masih berada di dalam buffer; kontrak # Safety di atas fungsi membebankannya ke pemanggil.
  3. Baris 11 (fn baca_byte_aman(buffer: &[u8], indeks: usize) -> Option<u8> {): fungsi pembungkus bertanda aman total — signature-nya hanya memuat tipe aman (&[u8], usize, Option<u8>), sehingga pemanggil tidak butuh unsafe sama sekali.
  4. Baris 12 (if indeks < buffer.len() {): invariant “offset berada dalam jangkauan” dibuktikan secara aman lewat pemeriksaan panjang slice — inilah pemindahan tanggung jawab dari konvensi ke kode.
  5. Baris 13 (Some(unsafe { baca_byte(buffer.as_ptr(), indeks) })): blok unsafe terkecil mungkin — satu pemanggilan, dibuka dan ditutup dalam satu ekspresi. Setelah baris ini, seluruh sisa program kembali aman penuh.
  6. Baris 22–25 (match baca_byte_aman(&data_paket, 2) { ... }): indeks 2 sah sehingga Some(0x7A) dicetak; hasil pembacaan sesuai elemen ketiga array.
  7. Baris 27–30 (match baca_byte_aman(&data_paket, 9) { ... }): indeks 9 ditolak sebelum pointer digeser sedikit pun — jalur None mencegah pemanggilan fungsi unsafe alih-alih membiarkannya gagal saat berjalan.

Pola baca_byte_aman adalah embrio dari safe abstraction: risiko dikurung di satu titik, bukti keamanan dinyatakan dalam kode aman, dan pemanggil tinggal menikmati API yang mustahil disalahgunakan. Chapter Safe Abstraction akan membangun versi jauh lebih serius dari pola ini.

Jebakan Blok yang Tidak Berisi Apa-apa #

Kompilator juga mengawasi arah sebaliknya: blok unsafe yang tidak memuat satu pun operasi berisiko memicu warning. Program berikut menunjukkan kasusnya:

rust
fn main() {
    unsafe {
        println!("Halo dari blok unsafe");
    }
}

Kompilasi berhasil, tetapi warning berikut muncul:

text
warning: unnecessary `unsafe` block
 --> main.rs:2:5
  |
2 |     unsafe {
  |     ^^^^^^ unnecessary `unsafe` block
  |
  = note: `#[warn(unused_unsafe)]` (part of `#[warn(unused)]`) on by default

warning: 1 warning emitted

Warning unnecessary unsafe block berguna sebagai alarm kebersihan: pagar yang melindungi apa-apa hanya menambah kebisingan dan menormalisasi rasa takut pada kompilator. Blok unsafe yang benar adalah yang hilang dengan sendirinya saat operasi berisikonya dihapus.

Ringkasan #

Blok unsafe memberi pagar lokal untuk lima operasi berisiko dan sebaiknya dibuat sekecil mungkin; fungsi unsafe menandai kontrak pada signature sehingga pemanggil wajib membungkus pemanggilan dengan blok unsafe di bawah ancaman E0133. Tubuh fungsi unsafe menjadi konteks unsafe otomatis pada edisi 2021, sementara edisi 2024 menuntut blok eksplisit melalui lint unsafe_op_in_unsafe_fn. Doc comment dengan bagian # Safety adalah tempat invariant dituliskan agar pemanggil tahu syarat aman, dan warning unnecessary unsafe block menjaga pagar tetap bermakna. Dengan dua kendaraan ini di tangan, kita siap mengenal alat paling klasik di dunia unsafe: raw pointer.