Kotak peralatan kita kini lengkap: panic! untuk menghentikan program, Result untuk kegagalan yang mengalir, unwrap_or untuk nilai cadangan, map dan map_err untuk transformasi, operator ? untuk propagasi, Box<dyn Error> untuk wadah umum, dan enum kustom untuk kosakata kegagalan domain. Kelengkapan justru melahirkan pertanyaan baru: untuk kegagalan berikutnya, alat mana yang dipilih? Keputusan tersebut menentukan kontrak fungsi, memengaruhi seluruh pemanggil, dan salah memilih kategori berujung pada program yang mati konyol atau hidup dalam keadaan rusak. Chapter penutup ini menyusun kerangka keputusan yang bisa kita pakai seumur menulis Rust, lengkap dengan dua program yang sama-sama benar meski memilih mekanisme berlawanan.

Pertanyaan Kunci: Siapa yang Bisa Memperbaikinya #

Kerangka keputusan berpusat pada satu pertanyaan: ketika kegagalan terjadi, siapa yang bisa memperbaikinya? Dua jawaban mungkin, dua mekanisme berbeda:

  • Hanya pemrogram, lewat perbaikan kode → panic. Pemanggil tidak punya tindakan runtime yang masuk akal; satu-satunya jalan keluar adalah memperbaiki logika yang melanggar invariant. Akses indeks di luar batas, kontrak fungsi yang dilanggar, atau angka yang seharusnya mustahil — semuanya bug.
  • Pemanggil, lewat keputusan runtime → Result. Keberhasilan pemanggil tidak bergantung pada kebenaran kode, melainkan pada keadaan dunia: teks yang diketik pengguna, isi berkas, respons jaringan. Kegagalan seperti ini wajar, terprediksi, dan harus punya jalur lanjutan.

Satu pertanyaan lanjutan mengasahnya: apakah masukan penyebab kegagalan bisa masuk lewat pintu yang sama sekali di luar kendali kode kita? Bila ya — formulir publik, berkas konfigurasi milik pengguna — jawabannya hampir pasti Result. Bila kegagalan hanya bisa terjadi karena kode kita sendiri salah menghitung, panic adalah laporan bug yang paling jujur.

Kontrak Fungsi yang Menuntut panic #

Fungsi average berikut punya kontrak yang tegas: parameter harus memuat setidaknya satu nilai. Kontrak tersebut dijaga dengan panic!:

rust
fn average(values: &[u32]) -> f64 {
    if values.is_empty() {
        panic!("average dipanggil dengan data kosong");
    }

    let mut total: u64 = 0;
    for &value in values {
        total += value as u64;
    }

    total as f64 / values.len() as f64
}

fn main() {
    let nilai_ujian = [80, 90, 100];
    println!("Rata-rata: {}", average(&nilai_ujian));
}

Output yang Dihasilkan:

text
Rata-rata: 90

Bedah singkatnya: pengecekan values.is_empty() berdiri di baris terdepan sebagai penjaga kontrak; sisanya menghitung total lalu membagi dengan panjang data tanpa pemeriksaan tambahan. Panggilan average(&[80, 90, 100]) memenuhi kontrak, sehingga program berjalan normal. Sekarang kita langgar kontraknya — kirim data kosong:

rust
fn average(values: &[u32]) -> f64 {
    if values.is_empty() {
        panic!("average dipanggil dengan data kosong");
    }

    let mut total: u64 = 0;
    for &value in values {
        total += value as u64;
    }

    total as f64 / values.len() as f64
}

fn main() {
    let kosong: [u32; 0] = [];
    println!("Rata-rata: {}", average(&kosong));
}

Pesan panic saat program berjalan:

text
thread 'main' (1928) panicked at main.rs:3:9:
average dipanggil dengan data kosong
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Mari kita telaah desainnya:

  1. Pesan average dipanggil dengan data kosong: pesan menyebut siapa yang dilanggar (average) dan apa kontraknya (data tidak boleh kosong) — bukan sekadar “terjadi kesalahan”. Pembaca panic langsung tahu harus memeriksa pemanggil, bukan isi fungsi.
  2. Lokasi main.rs:3:9: alamat menunjuk baris penjaga kontrak di dalam average, dan pemanggil yang bersalah — main — bisa ditelusuri lewat backtrace.
  3. Mengapa bukan Result: nilai rata-rata dari data kosong tidak punya jawaban yang benar. Mengembalikan Err akan memaksa seluruh pemanggil yang sehat untuk menangani cabang yang mustahil terjadi bila pemanggil benar — menggeser beban bug dari pelanggar ke semua penurut. Kontrak + panic menempatkan beban pada pihak yang tepat.

Desain ini sebangun dengan pustaka standar: prices[3] panic karena pemanggilnya bertanggung jawab menjamin indeks sah, sedangkan prices.get(3) mengembalikan Option untuk pemanggil yang tidak bisa menjamin. Pola kembar inilah yang kita tiru: average untuk kontrak terjamin, Result untuk keadaan tak terjamin.

Kegagalan Eksternal yang Menuntut Result #

Sekarang kebalikannya: fungsi yang membaca penilaian layanan dari teks pengguna. Masukan datang dari formulir publik, jadi semua cara gagal adalah kejadian normal yang harus ditangani:

rust
fn parse_rating(input: &str) -> Result<u8, String> {
    if input.is_empty() {
        return Err(String::from("Penilaian kosong, isi angka 1-5"));
    }

    match input.parse::<u8>() {
        Ok(nilai) if nilai >= 1 && nilai <= 5 => Ok(nilai),
        Ok(_) => Err(String::from("Nilai harus di rentang 1-5")),
        Err(_) => Err(String::from("Penilaian harus berupa angka")),
    }
}

fn main() {
    let masukan = ["5", "9", "bagus"];

    for input in masukan {
        match parse_rating(input) {
            Ok(nilai) => println!("Penilaian '{}' diterima: {}", input, nilai),
            Err(pesan) => println!("Penilaian '{}' ditolak: {}", input, pesan),
        }
    }
}

Output yang Dihasilkan:

text
Penilaian '5' diterima: 5
Penilaian '9' ditolak: Nilai harus di rentang 1-5
Penilaian 'bagus' ditolak: Penilaian harus berupa angka

Mari kita bedah kode di atas:

  1. Baris 2-4 (if input.is_empty()): teks kosong ditolak dengan Err — pada dunia formulir, kosong adalah masukan biasa, bukan pelanggaran kontrak.
  2. Baris 6-10 (match input.parse::<u8>()): tiga cabang memisahkan tiga keadaan: angka sah di rentang 1-5 (diterima), angka di luar rentang (ditolak dengan pesan rentang), dan teks non-angka (ditolak dengan pesan format). Guard if nilai >= 1 && nilai <= 5 menyaring cabang Ok sebelum diterima.
  3. Baris 16-21 (loop pemanggilan): ketiga masukan diproses tanpa satu pun program berhenti. Penilaian "9" dan "bagus" ditolak dengan pesan yang bisa langsung ditampilkan ke pengguna — persis perilaku yang kita harapkan dari layanan publik.

Kontras kedua program pada chapter ini adalah inti section: average panic karena pemanggilnya melanggar janji yang dikendalikan kode; parse_rating mengembalikan Err karena masukannya dikendalikan dunia luar. Keduanya sama-sama benar — keduanya hanya menjawab pertanyaan yang berbeda.

assert, Sepupu panic untuk Invariant #

Keluarga panic punya satu anggota penting yang perlu kita kenal sebelum menutup section: macro assert!. Macro tersebut mengevaluasi kondisi dan memicu panic ketika kondisi bernilai false:

rust
fn main() {
    let diskon: u32 = 25;

    assert!(diskon <= 100);
    println!("Diskon {} persen siap diterapkan", diskon);
}

Output yang Dihasilkan:

text
Diskon 25 persen siap diterapkan

Kondisi diskon <= 100 terpenuhi, sehingga program berjalan terus. Bila kita mengganti nilai menjadi 125, program berhenti dengan pesan assertion failed: diskon <= 100 — kondisi yang gagal tercetak otomatis. assert! adalah alat baku menjaga invariant di awal fungsi sekaligus tulang punggung test: kegagalan test dilaporkan sebagai panic dengan pesan kondisinya.

Peta Keputusan #

PertanyaanJawabanPilihan
Apakah pemanggil punya reaksi runtime yang masuk akal?Tidakpanic! / assert!
Apakah masukan datang dari dunia luar?YaResult
Apakah kegagalan menandakan invariant rusak?Yapanic!
Apakah kegagalan wajar terjadi pada program sehat?YaResult
Prototipe cepat, hasil pasti sukses?Sementaraunwrap / expect
Perlu cadangan saat gagal?Yaunwrap_or keluarga

Ringkasan #

Kerangka keputusannya satu pertanyaan: siapa yang bisa memperbaiki kegagalan. Bila hanya perbaikan kode yang menolong — invariant dilanggar, kontrak fungsi rusak — panic! atau assert! melaporkannya dengan pesan kontrak yang jelas, seperti fungsi average yang menolak data kosong. Bila pemanggil bisa bereaksi pada runtime — masukan pengguna, keadaan dunia luar — Result membawa kegagalan sebagai nilai, seperti parse_rating yang menolak penilaian tanpa mematikan program. Tabel keputusan menutup kotak peralatan section Penanganan Error. Banyak tipe di sekitar kita — Option<T>, Result<T, E>, Vec<T> — memakai satu sistem parameter tipe yang sama; mesin tersebut bernama generics, section berikutnya.