Setiap program yang berinteraksi dengan dunia luar akhirnya berhadapan dengan kegagalan: pengguna mengetik angka yang tidak valid, berkas konfigurasi ternyata hilang, jaringan terputus di tengah transaksi. Di sisi lain, kode kita sendiri juga bisa salah — indeks array melebihi panjang, atau fungsi dipanggil dengan keadaan yang seharusnya mustahil. Rust membagi seluruh kegagalan itu menjadi dua kategori besar: unrecoverable error yang menghentikan program lewat panic, dan recoverable error yang ditangani lewat enum Result. Pemisahan ini bukan gaya-gayaan, karena keduanya menuntut perlakuan yang berbeda total. Pada chapter ini kita akan memetakan kedua kategori dengan contoh nyata, sebagai bekal untuk seluruh section Penanganan Error.

Dua Wajah Kegagalan Program #

Mari kita mulai dari masalah yang mendasar: tidak semua kegagalan itu setara. Bayangkan dua kejadian pada aplikasi kasir toko kelontong. Kejadian pertama: kasir mengetik "abc" pada kolom jumlah barang — hal ini wajar terjadi setiap hari, dan program seharusnya menampilkan pesan lalu melanjutkan transaksi. Kejadian kedua: kode internal membaca elemen keempat dari array yang hanya berisi tiga elemen — tidak ada makna “memulihkan” dari keadaan seperti ini, karena logika program itu sendiri sudah rusak.

Kejadian pertama termasuk kategori recoverable: kegagalan yang bisa diantisipasi, ditangani, dan program tetap hidup. Kejadian kedua termasuk kategori unrecoverable: bug yang membuat sisa eksekusi program tidak bisa dipercaya lagi. Banyak bahasa pemrograman memakai satu mekanisme untuk keduanya — misalnya exception yang dilempar untuk salah ketik pengguna sekaligus untuk akses memori ilegal — sehingga bug dan data buruk tercampur dalam satu kantong, dan pemanggil fungsi sulit tahu kegagalan mana yang wajar ditangkap.

Rust menolak pencampuran tersebut. Kategori unrecoverable ditangani panic: program berhenti saat itu juga, lengkap dengan pesan lokasi kegagalan. Kategori recoverable ditangani Result: kegagalan menjadi nilai biasa yang mengalir keluar dari fungsi, dan pemanggil fungsilah yang memutuskan langkah berikutnya. Keputusan kategori mana yang dipakai terlihat langsung pada tanda tangan fungsi, bukan tersembunyi di dalam implementasi.

panic untuk Kategori Unrecoverable #

Ketika invariant program rusak, Rust menyediakan jalan keluar yang tegas: hentikan program sebelum kerusakan menjalar. Mekanisme tersebut bernama panic, dan bentuk paling eksplisitnya adalah macro panic! yang akan kita bahas tuntas pada chapter berikutnya. Selain macro tersebut, sejumlah operasi bawaan juga memicu panic sendiri — pengindeksan array di luar batas adalah salah satunya. Perhatikan program kasir berikut, yang mengambil nomor item dari masukan pengguna:

rust
fn main() {
    let prices = [1500, 2500, 3000];
    let input = "3";

    match input.parse::<usize>() {
        Ok(index) => println!("Harga item terpilih: {}", prices[index]),
        Err(error) => println!("Nomor item tidak terbaca: {}", error),
    }
}

Simpan kode tersebut sebagai main.rs, lalu kompilasi dan jalankan:

bash
rustc --edition 2021 main.rs -o program_kasir
./program_kasir
echo "Exit code: $?"

Output yang Dihasilkan:

text
thread 'main' (1919) panicked at main.rs:6:58:
index out of bounds: the len is 3 but the index is 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Exit code: 101

Angka 1919 dalam tanda kurung adalah nomor thread yang berbeda pada setiap eksekusi, jadi angka pada komputer kita tidak akan sama. Mari kita bedah kejadiannya:

  1. Baris 2 (let prices = [1500, 2500, 3000];): array berisi tiga harga, sehingga indeks yang sah hanya 0, 1, dan 2.
  2. Baris 3 (let input = "3";): masukan berupa teks "3" — anggap berasal dari kolom yang diisi kasir; teks ini memang berupa angka, sehingga parsing akan berhasil.
  3. Baris 5-8 (match input.parse::<usize>() { ... }): method parse mengembalikan Result dan kode sudah menangani kedua cabangnya dengan benar — cabang Ok untuk teks yang valid, cabang Err untuk teks yang bukan angka.
  4. Baris 6 (prices[index]): parsing "3" berhasil menghasilkan angka 3, tetapi indeks 3 tidak pernah sah untuk array yang berisi 1500, 2500, 3000. Inilah pelanggaran invariant: kode pemanggil lupa memeriksa batas, dan operasi pengindeksan memicu panic dengan pesan index out of bounds.
  5. Exit code 101: nilai kembalian proses ke sistem operasi saat panic; nilai ini berbeda dari 0 (sukses), sehingga skrip dan alat lain dapat mendeteksi bahwa program berakhir secara tidak normal.

Perhatikan pelajarannya: kegagalan parse ditangani rapi dengan Result, tetapi kegagalan indeks tidak bisa “ditangani” di titik yang sama — yang benar adalah memperbaiki kode dengan memvalidasi rentang indeks sebelum mengakses array. Panic adalah cara Rust berkata: bug ini harus diperbaiki di sumbernya, bukan ditutupi di jalur eksekusi.

Result untuk Kategori Recoverable #

Sekarang kebalikannya: kegagalan yang wajar, terprediksi, dan harus diterima sebagai bagian hidup program. Untuk kategori ini Rust memakai enum Result dengan dua variant — Ok(T) membawa nilai sukses dan Err(E) membawa kegagalan. Bentuknya menyerupai Option<T> yang kita kenal di section Struktur Data, tetapi dengan satu perbedaan penting: Err membawa informasi tentang kegagalan, bukan sekadar penanda ketiadaan. Program berikut membaca usia dari teks dan menangani kedua kemungkinan:

rust
fn main() {
    let input_valid = "36";
    let input_invalid = "abc";

    match input_valid.parse::<u32>() {
        Ok(age) => println!("Input '{}' terbaca sebagai usia {}", input_valid, age),
        Err(error) => println!("Input '{}' tidak bisa diproses: {}", input_valid, error),
    }

    match input_invalid.parse::<u32>() {
        Ok(age) => println!("Input '{}' terbaca sebagai usia {}", input_invalid, age),
        Err(error) => println!("Input '{}' tidak bisa diproses: {}", input_invalid, error),
    }
}

Output yang Dihasilkan:

text
Input '36' terbaca sebagai usia 36
Input 'abc' tidak bisa diproses: invalid digit found in string

Mari kita telaah kode di atas:

  1. Baris 2-3 (let input_valid dan let input_invalid): dua masukan teks mewakili dua keadaan nyata — satu berupa angka, satu salah ketik. Keduanya diproses dengan kode yang sama.
  2. Baris 5 (input_valid.parse::<u32>()): method parse mengembalikan Result<u32, ParseIntError>; teks "36" berhasil dikonversi menjadi Ok(36) dan cabang pertama mencetak usianya.
  3. Baris 9-12 (match input_invalid ...): teks "abc" tidak dapat dikonversi, sehingga parse mengembalikan Err(ParseIntError); cabang kedua mencetak isi error yang berbunyi invalid digit found in string.
  4. Baris terakhir program: setelah kedua match selesai, program masih hidup dan exit code tetap 0 — kegagalan parsing tidak menghentikan apa pun, karena kegagalan tersebut memang bagian dari kontrak fungsi.

Kontras kedua contoh tersebut tajam. Pada program kasir, program mati di tengah jalan karena invariant rusak. Pada program usia, kedua jalur sukses dan gagal selesai dengan tertib karena kegagalan direpresentasikan sebagai nilai.

Peta Perbandingan Dua Kategori #

Kita bisa merangkum pembagian kerja kedua mekanisme dalam satu tabel:

Aspekpanic (unrecoverable)Result (recoverable)
Makna kegagalanBug; invariant program rusakKejadian wajar yang diantisipasi
Sumber khasKesalahan kode internalData dari dunia luar
Setelah terjadiProgram berhenti totalProgram berjalan; pemanggil memutuskan
Exit code101Tetap ditentukan kode kita
ContohIndeks di luar batas, overflow mode debugParsing teks, berkas tidak ditemukan
Cara “menangani”Perbaiki kode di sumbernyaCabang Ok dan Err

Ringkasan #

Kegagalan program terbagi dua kategori: unrecoverable yang dilaporkan lewat panic dengan pesan lokasi dan exit code 101, serta recoverable yang mengalir sebagai nilai Result dengan variant Ok dan Err. Indeks array di luar batas adalah contoh bug yang layak menghentikan program, sedangkan teks yang gagal diparse adalah kegagalan wajar yang ditangani cabang Err tanpa mematikan proses. Pemilihan kategori terlihat langsung pada tanda tangan fungsi sehingga pemanggil tidak bisa mengabaikannya. Mekanisme panic sendiri masih menyimpan beberapa detail — bagaimana macro panic! dipanggil, apa yang terjadi pada memori saat panic, dan bagaimana membaca backtrace — yang akan kita bongkar pada chapter berikutnya.