Chapter sebelumnya menutup dengan kabar baik: membuat, menyalin, dan membandingkan raw pointer semuanya aman. Kabar buruknya menunggu di satu operasi terakhir yang menjadi tujuan sesungguhnya dari pointer: mengakses nilai di alamat yang ditunjuk. Operasi dereference — menulis tanda bintang * di depan pointer — adalah titik di mana semua jaminan reference hilang dan raw pointer harus membuktikan sendiri kesahihannya. Karena pembuktian itu tidak mungkin dilakukan kompilator, Rust mengurung operasi ini di balik pagar unsafe dan menolak kode lain dengan error E0133. Chapter ini membaca pesan error tersebut sampai ke catatan kakinya, lalu berlatih tiga pola dereferensi paling umum: baca, tulis, dan menjelajah array dengan aritmetika pointer .add().

Gerbang Wajib: Error E0133 #

Mari kita mulai dari penolakannya. Program berikut membuat raw pointer secara sah dari variabel hidup, lalu mencoba membaca nilainya di luar blok unsafe:

rust
fn main() {
    let tebusan: i32 = 250000;
    let ptr = &tebusan as *const i32;

    println!("Nilai tebusan: {}", *ptr);
}

Kompilasi gagal dengan pesan asli berikut:

text
error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
 --> main.rs:5:35
  |
5 |     println!("Nilai tebusan: {}", *ptr);
  |                                   ^^^^ dereference of raw pointer
  |
  = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior

error: aborting due to 1 previous error

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

Baris pertama pesan menyatakan intinya: dereferensi raw pointer hanya legal di dalam fungsi atau blok unsafe. Namun catatan di bawahnya adalah bagian paling edukatif — kompilator merinci lima penyakit yang mungkin diderita sebuah raw pointer: null (menunjuk alamat nol), dangling (menunjuk memori yang sudah tidak dimiliki siapa pun), unaligned (alamat tidak sejajar dengan kebutuhan tipe), pelanggaran aliasing rules, dan data races. Pada reference, kelima penyakit ini mustahil terjadi karena aturan lifetime dan borrowing membuktikan sebaliknya. Pada raw pointer, tidak ada pembuktian — sisanya adalah kepercayaan, dan unsafe adalah tanda tangan yang menyatakan kita bersedia menanggung akibatnya bila kepercayaan itu salah.

Membaca dan Menulis lewat Raw Pointer #

Pola pertama adalah pasangan baca-tulis pada satu nilai. Program berikut memperbaiki berat paket lewat dua raw pointer — satu baca-saja untuk memeriksa, satu baca-tulis untuk mengoreksi:

rust
fn main() {
    let mut berat_paket = 12;

    let ptr_baca = &berat_paket as *const i32;
    let ptr_tulis = &mut berat_paket as *mut i32;

    unsafe {
        println!("Berat sebelum koreksi: {}", *ptr_baca);
        *ptr_tulis = *ptr_tulis + 3;
        println!("Berat setelah koreksi: {}", *ptr_baca);
    }

    println!("Berat final: {}", berat_paket);
}

Output yang Dihasilkan:

text
Berat sebelum koreksi: 12
Berat setelah koreksi: 15
Berat final: 15

Mari kita bedah kode di atas baris per baris:

  1. Baris 2 (let mut berat_paket = 12;): nilai i32 di stack menjadi sumber kedua pointer. Selama berat_paket hidup, alamat yang ditunjuk pasti valid dan terinisialisasi — dua dari lima penyakit E0133 sudah terbukti absen.
  2. Baris 4–5 (let ptr_baca = ...; let ptr_tulis = ...;): pointer baca dan pointer tulis sama-sama menunjuk berat_paket. Pada dunia reference, kombinasi & dan &mut ini ditolak E0502; pada raw pointer, aliasing diperbolehkan dan konsekuensinya kita tanggung sendiri.
  3. Baris 8 (println!("Berat sebelum koreksi: {}", *ptr_baca);): *ptr_baca membaca empat byte di alamat pointer lalu menampilkannya. Pembacaan *const T seperti ini mengubah pointer menjadi nilai i32 biasa.
  4. Baris 9 (*ptr_tulis = *ptr_tulis + 3;): sisi kanan membaca 12 lewat dereferensi, sisi kiri menulis 15 ke alamat yang sama. Satu baris ini memuat dua operasi unsafe sekaligus — keduanya berada di dalam pagar yang benar.
  5. Baris 13 (println!("Berat final: {}", berat_paket);): variabel asli terbaca 15. Karena kedua pointer memang lahir dari variabel ini, hasilnya konsisten — bukti sederhana bahwa unsafe yang invariantnya dijaga tetap menghasilkan perilaku terprediksi.

Berjalan di Atas Array dengan .add() #

Pola kedua adalah pemanfaatan paling umum di dunia nyata: memproses elemen array satu per satu. Error E0369 di Chapter Raw Pointer sudah mengarahkan kita ke method .add(), dan sekarang waktunya memakainya:

rust
fn main() {
    let stok_gudang = [40, 15, 88, 23, 56];
    let ptr = stok_gudang.as_ptr();

    unsafe {
        let total: i32 = *ptr
            + *ptr.add(1)
            + *ptr.add(2)
            + *ptr.add(3)
            + *ptr.add(4);
        println!("Total stok gudang: {}", total);
        println!("Item pertama: {}, item terakhir: {}", *ptr, *ptr.add(4));
    }
}

Output yang Dihasilkan:

text
Total stok gudang: 222
Item pertama: 40, item terakhir: 56

Mari kita bedah kode di atas baris per baris:

  1. Baris 2 (let stok_gudang = [40, 15, 88, 23, 56];): array lima elemen menempati blok memori rapat tanpa celah. as_ptr() pada baris berikutnya selalu menghasilkan alamat elemen pertama.
  2. Baris 3 (let ptr = stok_gudang.as_ptr();): as_ptr() mengembalikan *const i32 penunjuk elemen pertama — cara memperoleh pointer yang valid, terinisialisasi, dan selaras tanpa berurusan dengan cast manual.
  3. Baris 6 (let total: i32 = *ptr): penjumlahan dibuka dengan dereferensi pointer dasar, yaitu elemen 40.
  4. Baris 7–10 (+ *ptr.add(1) ... + *ptr.add(4)): ptr.add(n) menggeser alamat sejauh n elemen — bukan n byte. Untuk i32, setiap geseran bernilai empat byte. Kesalahan klasik pemula adalah menganggap .add(1) berarti “satu byte ke depan”; pada tipe besar, salah paham ini melompat ke tengah elemen dan memicu pembacaan unaligned.
  5. Baris 12 (println!("Item pertama: {}, item terakhir: {}", *ptr, *ptr.add(4));): *ptr membaca elemen pertama (40) dan *ptr.add(4) membaca elemen kelima (56) — batas paling jauh yang masih sah untuk array berisi lima elemen.

Kontrak .add() memiliki tiga syarat yang harus kita jaga manual. Pertama, jumlah geseran harus membuat pointer hasil tetap berada di dalam alokasi yang sama, atau tepat satu elemen di belakang ujungnya — ptr.add(5) pada array ini boleh dihitung (posisi one-past-the-end), tetapi dereferensinya tetap undefined behavior. Kedua, tipe elemen di alokasi harus sama dengan T pada pointer; menggeser pointer *const i32 melintasi memori milik tipe lain melanggar aliasing. Ketiga, overflow jumlah elemen menghasilkan pointer tak bermakna. Method .offset(isize) memakai kontrak serupa dengan kemampuan menggeser mundur lewat angka negatif, sedangkan .wrapping_add() hanya menjamin perhitungan aritmetikanya yang bebas undefined behavior — dereferensi hasilnya tetap menuntut validitas penuh.

Daftar Periksa Sebelum Menekan Tombol * #

Semua contoh di atas aman karena lahir dari pola yang sama: pointer diperoleh dari data yang jelas hidupnya, dan penggeserannya terkendali. Sebelum menulis blok unsafe sendiri, kita perlu menguji kelima pertanyaan berikut terhadap setiap dereferensi: apakah pointer pasti tidak null? Apakah data yang ditunjuk masih hidup dan belum di-drop? Apakah alamat selaras dengan kebutuhan tipe? Apakah memori sudah terinisialisasi dengan nilai T yang sah? Dan bila pointer bisa menulis, apakah tidak ada penunjuk lain yang aktif pada data yang sama saat itu juga? Satu jawaban “tidak” berarti program memasuki wilayah undefined behavior — dan ingat, undefined behavior sering tidak menampakkan diri saat pengujian.

Ringkasan #

Dereferensi raw pointer dengan operator * hanya legal di dalam blok atau fungsi unsafe; pelanggarannya ditolak error E0133 yang merinci lima risiko — null, dangling, unaligned, aliasing, dan data race — sebagai undefined behavior. Pola baca-tulis satu nilai bekerja konsisten selama pointer lahir dari variabel yang masih hidup, sementara penjelajahan array memakai .add(n) yang menggeser sebesar n elemen dengan kontrak in-bounds yang harus kita jaga manual. Sintaks &raw const/&raw mut menyediakan jalur pembuatan pointer tanpa reference sejak Rust 1.82. Satu senjata unsafe masih tersimpan: variabel global static mut yang mempertemukan semua pelajaran ini dengan dunia multi-thread — pembahasan Chapter Static Mutable.