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:
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:
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 backtraceMari kita bedah kode di atas:
- Baris 2 (
println!("Kasir mulai ...")): baris ini tercetak normal, membuktikan program berjalan sebelum panic terjadi. - Baris 4 (
let total_items = 0;): nilai nol mensimulasikan keadaan mustahil secara domain — kasir menekan tombol hitung tanpa satu pun barang terdaftar. - Baris 5-6 (
if total_items == 0 { panic!(...) }): pengecekan invariant. Kondisi terlanggar,panic!dieksekusi dengan pesan berformat{}yang menyisipkan nilaitotal_items, persis seperti format padaprintln!. - Baris 9 (
println!("Kasir selesai ...")): baris ini tidak pernah tercetak — begitupanic!dieksekusi, eksekusi fungsimainberhenti 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:
thread 'main' (3008): panic terjadi di threadmain— thread utama tempat fungsimainberjalan. Angka3008dalam tanda kurung adalah nomor thread sistem operasi yang berbeda pada setiap eksekusi, jadi angka di komputer kita tidak akan sama.panicked at main.rs:6:9: alamat kejadian dalam formatberkas:baris:kolom— baris ke-6 tempatpanic!ditulis, kolom ke-9 posisi awal katapanic!setelah delapan spasi indentasi.Daftar belanja kosong (0 item), total tidak bisa dihitung: isi pesan yang kita tulis sendiri di dalampanic!— sudah terisi nilai0dari placeholder.note: run withRUST_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:
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:
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 pelangganMari kita telaah urutan kejadiannya:
- Baris 12 (
let _guard = PaymentGuard { order_id: 4021 };):process_ordermembuat penjaga pembayaran; prefiks underscore pada_guardhanya membungkam peringatan variabel tak terpakai, kepemilikan tetap berlaku penuh sehinggaDroppasti berjalan saat_guardjatuh. - Baris 13 (
panic!("Koneksi ke payment gateway terputus");): panic terjadi di dalamprocess_order, tepat di baris dan kolom yang tercatat pada pesan. - Baris ke-2 sampai ke-4 pada output: pesan panic tercetak lebih dulu — panic hook menyampaikan laporan sebelum proses pembersihan dimulai.
- Baris terakhir output (
Order 4021 dibatalkan, ...): baris ini berasal dari methoddropmilikPaymentGuard: unwinding menggulung stack dariprocess_order, menemukan_guardmasih hidup, dan menjalankanDrop— dana pelanggan dikembalikan meskipun program sekarat. - 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:
RUST_BACKTRACE=1 ./program_kasirOutput yang Dihasilkan (salah satu eksekusi di komputer penulis):
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.