panic! vs Result

Kapan program digugurkan serta filosofi unwinding dan proses pemulihan.

Daftar isi

Topik ini membahas panic! sebagai mekanisme kegagalan yang mengakhiri program, serta kriteria pemilihannya dibandingkan Result yang memelihara jalur pemulihan. Pembahasan mencakup bentuk pesan panic beserta lokasinya, proses unwinding yang membersihkan memori saat program berhenti, penyebab panic dari pemeriksaan runtime seperti pengindeksan di luar batas, dan panduan praktis kapan kegagalan sebaiknya dinyatakan mustahil dipulihkan. Setelah membaca topik ini, keputusan antara menggugurkan program dan mengembalikan Err dapat diambil dengan alasan yang eksplisit, dan pesan panic yang muncul dapat dibaca sampai ke sumber kejadiannya.

Apa yang terjadi saat panic#

Macro panic! menghentikan alur program pada baris pemanggilannya. Sebelum program benar-benar berakhir, Rust mencetak pesan yang memuat nama thread, lokasi berkas, baris, dan kolom pemanggilan, serta pesan yang menjadi argumen macro tersebut. Program berikut memanggil panic! di tengah pencetakan agar urutan keluarannya terlihat.

bash
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang memanggil macro panic!
printf 'fn main() {\n    println!("sebelum panic");\n    panic!("suhu sensor melebihi batas");\n}\n' > panik-sensor.rs
# kompilasi lalu jalankan, nomor thread pada pesan bervariasi sehingga disaring
rustc --edition=2021 panik-sensor.rs -o panik-sensor && ./panik-sensor 2>&1 | sed -E 's/ \([0-9]+\)//' | sed -n '1,5p'
text Keluaran
sebelum panic

thread 'main' panicked at panik-sensor.rs:3:5:
suhu sensor melebihi batas
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Baris sebelum panic tercetak lebih dahulu karena macro panic! baru dijalankan pada baris ketiga. Pesan panic kemudian memuat lokasi panik-sensor.rs:3:5, yaitu posisi pemanggilan macro itu sendiri, diikuti pesan suhu sensor melebihi batas. Baris terakhir keluaran adalah catatan mengenai variabel lingkungan RUST_BACKTRACE untuk menampilkan jejak tumpukan. Nomor thread yang sebenarnya tercetak setelah kata main berbeda pada setiap jalankan sehingga disaring dari contoh, dan program berakhir dengan kode keluar 101.

Penyetelan RUST_BACKTRACE=1 mengganti catatan itu dengan daftar bingkai tumpukan yang menunjukkan rantai pemanggilan sampai ke titik panic.

bash
export PATH="$HOME/.cargo/bin:$PATH"
# jalankan program yang sama dengan jejak tumpukan aktif
RUST_BACKTRACE=1 ./panik-sensor 2>&1 | sed -E 's/ \([0-9]+\)//' | sed -n '1,13p'
text Keluaran
sebelum panic

thread 'main' panicked at panik-sensor.rs:3:5:
suhu sensor melebihi batas
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/58cf8f9c35b355199727627d07ae34a4e2846d67/library/std/src/panicking.rs:679:5
   1: core::panicking::panic_fmt
             at /rustc/58cf8f9c35b355199727627d07ae34a4e2846d67/library/core/src/panicking.rs:80:14
   2: panik_sensor::main
   3: <fn() as core::ops::function::FnOnce<()>>::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Jejak tersebut menunjukkan bahwa panic berasal dari panik_sensor::main setelah melewati fungsi pembantu di dalam pustaka standar. Bingkai dengan awalan __rustc atau core berasal dari pustaka standar, sedangkan bingkai yang bernama program sendiri menandai kode yang ditulis pengembang. Nilai full pada RUST_BACKTRACE=full menampilkan jejak yang lebih rinci beserta alamat memori, tetapi untuk mencari lokasi panic pada program sendiri, mode 1 biasanya sudah memadai.

Panic juga dapat timbul tanpa macro panic!, yaitu dari pemeriksaan yang dijalankan runtime ketika program hendak melakukan operasi yang tidak aman. Pengindeksan array dengan tanda kurung siku termasuk operasi yang diawasi pemeriksaan tersebut, dan contoh berikut memakai indeks yang dihitung saat program berjalan sehingga nilainya berada di luar panjang array.

bash
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang mengakses indeks di luar batas array
printf 'fn main() {\n    let suhu = [21, 24, 19];\n    let indeks = suhu.len() + 7;\n    println!("{}", suhu[indeks]);\n}\n' > indeks-suhu.rs
# kompilasi lalu jalankan untuk melihat panic dari pemeriksaan batas
rustc --edition=2021 indeks-suhu.rs -o indeks-suhu && ./indeks-suhu 2>&1 | sed -E 's/ \([0-9]+\)//' | sed -n '1,4p'
text Keluaran
thread 'main' panicked at indeks-suhu.rs:4:20:
index out of bounds: the len is 3 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Pesan index out of bounds menyebut panjang array dan nilai indeks yang diminta, sehingga penyebabnya dapat diperbaiki tanpa menebak. Alternatif yang aman terhadap batas adalah metode get, yang mengembalikan Option alih-alih memicu panic, dan memilih di antara keduanya berarti memilih antara menggugurkan program dan menangani ketidakhadiran nilai. Setelah panic terjadi, pembersihan memori dijalankan melalui unwinding, yaitu penelusuran tumpukan dari titik panic ke arah luar sambil menjalankan drop bagi setiap nilai yang masih hidup. Berkas yang terbuka ditutup dan memori heap dikembalikan selama proses tersebut, kemudian program berakhir dengan kode keluar 101. Konfigurasi Cargo dapat mengubah perilaku ini menjadi penghentian langsung tanpa unwinding melalui panic = "abort" pada profil tertentu, yang dipakai untuk memperkecil ukuran binary.

panic! untuk kondisi tak terpulihkan#

Panic dipilih ketika kegagalan menandakan program yang salah, bukan dunia luar yang sedang berbeda dari biasanya. Pelanggaran invarian internal adalah contohnya, yaitu sebuah fungsi yang menjanjikan hanya menerima nilai pada rentang tertentu tetapi menerima nilai di luar rentang itu, sehingga kelanjutan perhitungan tidak bermakna. Kegagalan semacam itu tidak dapat dipulihkan oleh kode yang sama karena koreksinya berada pada kode yang salah menuliskan nilai tersebut. Data yang telah rusak juga lebih baik dihentikan daripada ditularkan ke berkas atau jaringan karena kelanjutannya hanya menyebarkan kerusakan.

Contoh dan prototipe menempati posisi khusus. Buku resmi Rust menyatakan bahwa unwrap, expect, dan panic! wajar dipakai ketika tujuan kode sekadar memperlihatkan cara kerja, termasuk pada pengujian yang memang seharusnya gagal total bila kondisinya tidak terpenuhi. Pada contoh semacam itu, penanganan penuh hanya mengaburkan pokok yang sedang didemonstrasikan. Begitu kode bergerak menuju jalur data nyata, titik panic yang tidak disengaja perlu dicari dan diganti dengan penanganan yang sesungguhnya.

Result untuk kegagalan yang diharapkan#

Kegagalan yang dapat diramalkan sebaiknya tidak menjadi panic. Masukan pengguna yang salah format, berkas konfigurasi yang belum ada, dan jaringan yang putus adalah kejadian normal ketika program berjalan, bukan bug. Tipe Result menyatakan kejadian itu sebagai nilai yang dapat diperiksa, sehingga pemanggil tetap memegang kendali untuk memberi nilai cadangan, mencoba ulang, atau berhenti dengan pesan yang layak. Tabel dan diagram berikut merangkum perbedaan kedua jalur tersebut.

Aspekpanic!Result
makna kegagalanprogram salah atau invarian rusakkejadian yang diharapkan terjadi
penanganantidak ada, program berhentidiputuskan oleh pemanggil
bentuk keluaranpesan, lokasi, dan unwindingnilai Err yang dapat diperiksa
contoh kejadianindeks di luar batas, invarian rusakteks salah format, berkas tidak ada

Diagram tersebut menyandingkan kedua jalur dari pemicunya sampai akibat akhirnya. Jalur atas berawal dari kondisi tak terpulihkan yang berakhir pada penghentian program dengan kode keluar 101, sedangkan jalur bawah berawal dari kegagalan yang diharapkan dan berujung pada keputusan pemanggil, baik memberi nilai cadangan, mencoba lagi, maupun berhenti.

kondisi tak terpulihkanpanic!pesan, lokasi, unwinding,program berhenti (exit 101)panic! mengakhiri program, sedangkan Err menyerahkan keputusan kepada pemanggil.kegagalan yang diharapkanErr(E)pemanggil memilih: nilai cadangan,coba lagi, atau berhenti
Perbedaan jalur panic! dan Result

Pemilihan dapat diarahkan dengan satu uji sederhana, yaitu menentukan pihak yang mampu merespons kegagalan itu. Bila hanya penulis kode yang dapat memperbaiki keadaannya, panic memperpendek jalan menuju perbaikan karena gejalanya langsung terlihat. Bila pihak yang mampu merespons adalah pengguna akhir atau lingkungan eksternal, Result menyerahkan pilihan kepada pihak tersebut. Sebuah pustaka pembaca berkas konfigurasi sebaiknya mengembalikan Result karena isi berkas adalah urusan pemanggilnya, sedangkan pemeriksaan internal yang menemukan keadaan mustahil boleh panic karena pemanggil tidak dapat berbuat apa pun terhadap keadaan tersebut.

Rust menyediakan std::panic::catch_unwind untuk menangkap efek panic pada batas tertentu, misalnya pada kerangka pengujian atau pada kode yang menjalankan modul dari pihak lain. Alat ini bukan pengganti Result karena panic tetap dimaksudkan sebagai keadaan luar biasa, dan pemulihan lewat penangkapan tidak menjamin data yang sedang diolah tetap konsisten. Kegagalan yang diharapkan sejak awal dinyatakan sebagai Result, bukan dijadikan panic yang kemudian ditangkap.

Kesalahan umum#

Kesalahan pertama adalah memakai panic untuk kegagalan yang diharapkan, misalnya menggugurkan program karena berkas konfigurasi belum ada pada saat program dijalankan pertama kali. Pengguna kehilangan kesempatan membuat berkas tersebut, padahal kegagalan semacam ini adalah kasus klasik Result. Kembalikan error yang menjelaskan berkas yang dicari, lalu biarkan pemanggil memutuskan langkah berikutnya.

Kesalahan kedua adalah membiarkan unwrap tinggal di dalam pustaka. Pustaka tidak mengetahui invarian pemanggilnya, sehingga None atau Err yang wajar bagi pemanggil berubah menjadi crash pada program orang lain. Kembalikan Option atau Result dari API publik, dan simpan expect hanya untuk keadaan yang benar-benar mustahil menurut kontrak pustaka itu sendiri.

Kesalahan ketiga adalah mengharapkan panic dapat dicoba layaknya exception. Bahasa dengan mekanisme try dan catch menjadikan aliran exception sebagai bagian kendali alur biasa, sedangkan Rust memisahkan jalur itu secara tegas. Membangun alur kerja yang bergantung pada panic yang ditangkap catch_unwind menghasilkan kode yang rapuh terhadap perubahan versi dan sulit dibaca. Nyatakan kegagalan yang diharapkan sebagai Result sejak awal.

Ringkasan#

  • panic! menghentikan program dengan pesan, lokasi, pembersihan memori lewat unwinding, dan kode keluar 101.
  • Panic juga timbul dari pemeriksaan runtime seperti pengindeksan di luar batas, dan jejaknya dapat diperluas dengan RUST_BACKTRACE.
  • panic! dipilih untuk pelanggaran invarian dan kondisi mustahil dipulihkan, sedangkan Result untuk kegagalan yang diharapkan seperti masukan salah atau berkas tidak ada.
  • Contoh dan prototipe boleh memakai unwrap dan expect, tetapi jalur data nyata menuntut keduanya diganti dengan penanganan yang eksplisit.

Referensi#