Chapter sebelumnya menetapkan pembagian tugas: panic menangani kegagalan kategori unrecoverable, Result menangani kegagalan kategori recoverable. Sekarang kita bongkar mesin panic itu sendiri — dari cara memanggilnya, anatomi pesan yang dicetaknya, proses unwind yang membersihkan memori setelahnya, hingga backtrace yang menuntun kita ke baris penyebab. panic bukan tombol darurat yang harus dihindari, melainkan pernyataan eksplisit bahwa kondisi program melanggar asumsi dan satu-satunya tindakan benar adalah berhenti. Pada akhir chapter ini kita akan bisa membaca setiap komponen pesan panic seperti membaca laporan kecelakaan lengkap dengan alamatnya.

Menghentikan Program dengan panic! #

Bentuk paling langsung dari panic adalah macro panic!. Macro tersebut menerima teks pesan, dan seperti println!, macro ini juga mendukung placeholder {} untuk menyisipkan nilai ke dalam pesan. Program kasir berikut memeriksa daftar belanja sebelum menghitung total:

rust
fn main() {
    println!("Kasir mulai menghitung total belanja");

    let total_items = 0;
    if total_items == 0 {
        panic!("Daftar belanja kosong ({} item), total tidak bisa dihitung", total_items);
    }

    println!("Kasir selesai menghitung total belanja");
}

Output yang Dihasilkan:

text
Kasir mulai menghitung total belanja
thread 'main' (3008) panicked at main.rs:6:9:
Daftar belanja kosong (0 item), total tidak bisa dihitung
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Mari kita bedah kode di atas:

  1. Baris 2 (println!("Kasir mulai ...")): baris ini tercetak normal, membuktikan program berjalan sebelum panic terjadi.
  2. Baris 4 (let total_items = 0;): nilai nol mensimulasikan keadaan mustahil secara domain — kasir menekan tombol hitung tanpa satu pun barang terdaftar.
  3. Baris 5-6 (if total_items == 0 { panic!(...) }): pengecekan invariant. Kondisi terlanggar, panic! dieksekusi dengan pesan berformat {} yang menyisipkan nilai total_items, persis seperti format pada println!.
  4. Baris 9 (println!("Kasir selesai ...")): baris ini tidak pernah tercetak — begitu panic! dieksekusi, eksekusi fungsi main berhenti total dan baris setelahnya tidak pernah dijalankan.

Pesan panic dikirim ke stderr, jalur keluaran khusus pesan kesalahan, sementara println! mengirim ke stdout. Kedua jalur berakhir di layar terminal yang sama, tetapi operator 2> pada shell dapat memisahkannya — berguna saat kita ingin mencatat log tanpa pesan panic di dalamnya.

Anatomi Pesan panic #

Pesan panic tampak padat, tetapi setiap komponennya punya makna. Kita urai satu per satu:

  1. thread 'main' (3008): panic terjadi di thread main — thread utama tempat fungsi main berjalan. Angka 3008 dalam tanda kurung adalah nomor thread sistem operasi yang berbeda pada setiap eksekusi, jadi angka di komputer kita tidak akan sama.
  2. panicked at main.rs:6:9: alamat kejadian dalam format berkas:baris:kolom — baris ke-6 tempat panic! ditulis, kolom ke-9 posisi awal kata panic! setelah delapan spasi indentasi.
  3. Daftar belanja kosong (0 item), total tidak bisa dihitung: isi pesan yang kita tulis sendiri di dalam panic! — sudah terisi nilai 0 dari placeholder.
  4. note: run with RUST_BACKTRACE=1 ...: petunjuk untuk menampilkan jejak pemanggilan; kita akan memakainya beberapa bagian di bawah.

Panic hook mengawali pesannya dengan satu baris kosong di terminal agar laporan terpisah rapi dari output program sebelumnya. Kombinasi berkas:baris:kolom inilah yang membuat panic mudah dilacak — kita tidak perlu menebak fungsi mana yang meledak karena alamatnya tertulis lengkap.

Unwind: Pembersihan Stack Saat panic #

Apa yang terjadi setelah pesan tercetak? Rust menjalankan proses bernama unwinding: stack digulung mundur frame demi frame dari titik panic ke arah main, dan setiap nilai yang masih hidup di tiap frame dijatuhkan dengan menjalankan method Drop miliknya — materi yang kita kenal dari section Ownership dan Memory. Program berikut memasang struct penjaga pembayaran yang implementasi Drop-nya mengembalikan dana pelanggan:

rust
struct PaymentGuard {
    order_id: u32,
}

impl Drop for PaymentGuard {
    fn drop(&mut self) {
        println!("Order {} dibatalkan, dana dikembalikan ke pelanggan", self.order_id);
    }
}

fn process_order() {
    let _guard = PaymentGuard { order_id: 4021 };
    panic!("Koneksi ke payment gateway terputus");
}

fn main() {
    println!("Memproses pembayaran order 4021");
    process_order();
    println!("Pembayaran selesai");
}

Output yang Dihasilkan:

text
Memproses pembayaran order 4021
thread 'main' (1940) panicked at main.rs:13:5:
Koneksi ke payment gateway terputus
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Order 4021 dibatalkan, dana dikembalikan ke pelanggan

Mari kita telaah urutan kejadiannya:

  1. Baris 12 (let _guard = PaymentGuard { order_id: 4021 };): process_order membuat penjaga pembayaran; prefiks underscore pada _guard hanya membungkam peringatan variabel tak terpakai, kepemilikan tetap berlaku penuh sehingga Drop pasti berjalan saat _guard jatuh.
  2. Baris 13 (panic!("Koneksi ke payment gateway terputus");): panic terjadi di dalam process_order, tepat di baris dan kolom yang tercatat pada pesan.
  3. Baris ke-2 sampai ke-4 pada output: pesan panic tercetak lebih dulu — panic hook menyampaikan laporan sebelum proses pembersihan dimulai.
  4. Baris terakhir output (Order 4021 dibatalkan, ...): baris ini berasal dari method drop milik PaymentGuard: unwinding menggulung stack dari process_order, menemukan _guard masih hidup, dan menjalankan Drop — dana pelanggan dikembalikan meskipun program sekarat.
  5. Baris 19 (println!("Pembayaran selesai");): tidak pernah tercetak; setelah unwind selesai, proses keluar.

Alur lengkapnya dapat kita petakan sebagai berikut:

Diagram di atas menegaskan urutan pada output: laporan dulu, pembersihan kemudian, keluar terakhir. Unwind menjadikan panic tetap sopan terhadap sumber daya — koneksi basis data, berkas, dan kunci yang modelnya memakai Drop ditutup rapi sebelum program benar-benar mati.

Membaca Backtrace #

Alamat main.rs:baris:kolom menunjuk titik ledak, tetapi tidak jalur yang menuju ke sana. Variabel lingkungan RUST_BACKTRACE=1 meminta Rust mencetak backtrace: daftar frame pemanggilan dari titik panic hingga main. Jalankan ulang program kasir:

bash
RUST_BACKTRACE=1 ./program_kasir

Output yang Dihasilkan (salah satu eksekusi di komputer penulis):

text
thread 'main' (3010) panicked at main.rs:6:9:
Daftar belanja kosong (0 item), total tidak bisa dihitung
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/std/src/panicking.rs:679:5
   1: core::panicking::panic_fmt
             at /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/panicking.rs:80:14
   2: main::main
   3: <fn() as core::ops::function::FnOnce<()>>::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Cara membacanya dari bawah ke atas: frame main::main menunjukkan pemanggilan terakhir milik kode kita, frame di atasnya adalah mesin panic internal milik pustaka standar. Pada program unwind tadi, backtrace memuat dua frame kode kita — main::process_order lalu main::main — sehingga jalur kejadian terbaca lengkap. Panjang hash pada path /rustc/... dan nomor baris di dalamnya berbeda-beda tergantung versi rustc dan mesin; yang stabil adalah urutan frame milik kode kita sendiri. Nilai RUST_BACKTRACE=full mencetak versi lebih rinci bila diperlukan.

Ringkasan #

Macro panic! menghentikan program dengan pesan berformat, mencatat alamat berkas:baris:kolom beserta nama thread, kemudian menjalankan unwinding yang memanggil Drop untuk setiap nilai yang masih hidup — buktinya penjaga pembayaran tetap mengembalikan dana pelanggan saat program sekarat. Variabel RUST_BACKTRACE=1 menambahkan jejak pemanggilan untuk menelusuri jalur menuju titik panic, dan proses yang mati karena panic melaporkan exit code 101. Mekanisme kategori unrecoverable kini tuntas; kita beralih ke pasangannya: enum Result yang membawa kegagalan kategori recoverable sebagai nilai biasa — chapter berikutnya.