1. Dua Jenis Error di Rust
Rust membagi error menjadi dua kategori utama, dan masing-masing ditangani dengan cara yang berbeda. Ini berbeda dari bahasa seperti Java atau Python yang menggunakan exception untuk semua jenis error.
| Kategori | Contoh | Penanganan | Tipe Rust |
|---|---|---|---|
| Unrecoverable | Bug programmer, data corrupt | Program berhenti (panic) | panic! |
| Recoverable | File tidak ditemukan, jaringan putus | Ditangani oleh programmer | Result<T, E> |
| Nilai Kosong | Elemen tidak ada, pencarian gagal | Ditangani dengan Option | Option<T> |
┌─────────────────────────────────────────────────────────────┐
│ ERROR HANDLING DI RUST │
│ │
│ ┌─────────────────────┐ ┌──────────────────────────┐ │
│ │ Unrecoverable Error │ │ Recoverable Error │ │
│ │ (Bug / Kritis) │ │ (Bisa dipulihkan) │ │
│ │ │ │ │ │
│ │ panic!("message") │ │ Result<T, E> │ │
│ │ → program crash │ │ → Ok(value) atau │ │
│ │ → stack unwinding │ │ Err(error) │ │
│ │ │ │ │ │
│ │ Contoh: │ │ Option<T> │ │
│ │ - Index out of │ │ → Some(value) atau │ │
│ │ bounds │ │ None │ │
│ │ - Divide by zero │ │ │ │
│ │ - Assert failure │ │ Contoh: │ │
│ └─────────────────────┘ │ - File not found │ │
│ │ - Parse error │ │
│ │ - Network timeout │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Filosofi Rust: error adalah bagian dari tipe fungsi. Jika sebuah fungsi bisa gagal, tipe return-nya harus mencerminkan hal itu. Ini memaksa programmer untuk menangani error secara eksplisit.
2. Panic! — Unrecoverable Errors
panic! adalah macro yang menyebabkan program berhenti seketika (crash). Gunakan hanya untuk error yang benar-benar tidak bisa dipulihkan — seperti bug programmer atau kondisi yang seharusnya tidak pernah terjadi.
Kapan Menggunakan panic!
fn main() {
// panic! langsung — program crash dengan pesan
// panic!("Terjadi kesalahan kritis!");
// Contoh panic yang terjadi otomatis:
let v = vec![1, 2, 3];
// v[99]; // panic: index out of bounds
// Unwrap pada None
let x: Option<i32> = None;
// x.unwrap(); // panic: called `Option::unwrap()` on a `None` value
// Unwrap pada Err
let result: Result<i32, &str> = Err("gagal");
// result.unwrap(); // panic: called `Result::unwrap()` on an `Err` value
// Panic dengan format string
let umur = -5;
if umur < 0 {
panic!("Umur tidak boleh negatif: {}", umur);
}
}
// Contoh fungsi yang valid menggunakan panic:
fn bagi(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("Tidak bisa membagi dengan nol!");
}
a / b
}
// Lebih baik gunakan Result untuk kasus yang bisa dipulihkan:
fn bagi_am(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Pembagian dengan nol"))
} else {
Ok(a / b)
}
}
Backtrace — Melacak Lokasi Panic
# Jalankan dengan RUST_BACKTRACE=1 untuk melihat stack trace RUST_BACKTRACE=1 cargo run # Output: # thread 'main' panicked at 'index out of bounds', src/main.rs:5:5 # stack backtrace: # 0: rust_begin_unwind # 1: core::panicking::panic_fmt # 2: core::panicking::panic_bounds_check # 3: my_project::main # ... # Untuk backtrace lengkap: RUST_BACKTRACE=full cargo run
Panic vs Abort
# Default: panic = "unwind" (stack unwinding, cleanup dijalankan) # Untuk binary yang lebih kecil: panic = "abort" (langsung berhenti) [profile.release] panic = "abort" # Binary lebih kecil, tidak ada cleanup
- Boleh panic: contoh, prototyping, test, kondisi yang mustahil terjadi
- Hindari panic: di library yang digunakan orang lain, di kode produksi
- Selalu gunakan Result: untuk operasi I/O, network, parsing, dan input pengguna
3. Result<T, E> — Recoverable Errors
Result adalah enum bawaan Rust yang digunakan untuk menangani error yang bisa dipulihkan. Ini adalah cara utama untuk menangani error di Rust.
Definisi Result
// Result sudah didefinisikan di standard library:
// enum Result<T, E> {
// Ok(T), // Operasi berhasil, berisi nilai
// Err(E), // Operasi gagal, berisi error
// }
use std::fs::File;
use std::io::{self, Read};
fn main() {
// Membuka file — bisa berhasil atau gagal
let hasil = File::open("data.txt");
// Match untuk menangani kedua kemungkinan
match hasil {
Ok(file) => println!("File berhasil dibuka: {:?}", file),
Err(error) => println!("Gagal membuka file: {}", error),
}
// Lebih spesifik: match berdasarkan jenis error
let hasil = File::open("data.txt");
match hasil {
Ok(file) => println!("File dibuka"),
Err(error) => match error.kind() {
io::ErrorKind::NotFound => println!("File tidak ditemukan"),
io::ErrorKind::PermissionDenied => println!("Akses ditolak"),
_ => println!("Error lain: {}", error),
},
}
}
// Fungsi yang mengembalikan Result
fn baca_file_ke_string(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut konten = String::new();
file.read_to_string(&mut konten)?;
Ok(konten)
}
Method pada Result
fn main() {
let ok_value: Result<i32, &str> = Ok(42);
let err_value: Result<i32, &str> = Err("gagal");
// is_ok() dan is_err()
println!("ok is_ok: {}", ok_value.is_ok()); // true
println!("err is_err: {}", err_value.is_err()); // true
// map — transformasi nilai Ok
let mapped = ok_value.map(|x| x * 2);
println!("mapped: {:?}", mapped); // Ok(84)
// map_err — transformasi error
let mapped_err = err_value.map_err(|e| format!("Error: {}", e));
println!("mapped_err: {:?}", mapped_err); // Err("Error: gagal")
// and_then (flatmap) — operasi berantai yang bisa gagal
let hasil: Result<i32, &str> = Ok(10)
.and_then(|x| if x > 0 { Ok(x * 2) } else { Err("negatif") });
println!("and_then: {:?}", hasil); // Ok(20)
// unwrap_or — nilai default jika Err
let nilai = err_value.unwrap_or(0);
println!("unwrap_or: {}", nilai); // 0
// unwrap_or_else — closure untuk nilai default
let nilai = err_value.unwrap_or_else(|e| {
println!("Warning: {}, menggunakan default", e);
0
});
// unwrap_or_default — default dari Default trait
let nilai: i32 = err_value.unwrap_or_default();
println!("unwrap_or_default: {}", nilai); // 0
// map_or — unwrap dengan transformasi
let hasil = ok_value.map_or(0, |x| x * 10);
println!("map_or: {}", hasil); // 420
// transpose — Result<Option<T>> → Option<Result<T>>
let x: Result<Option<i32>, &str> = Ok(Some(5));
let y: Option<Result<i32, &str>> = x.transpose();
println!("transpose: {:?}", y); // Some(Ok(5))
}
4. Unwrap dan Expect
unwrap() dan expect() adalah cara cepat untuk mengekstrak nilai dari Result atau Option, tetapi keduanya akan panic jika nilai tidak tersedia.
use std::fs;
fn main() {
// unwrap — ambil nilai, panic jika Err/None
let ok: Result<i32, &str> = Ok(42);
let nilai = ok.unwrap(); // 42
println!("unwrap: {}", nilai);
// let err: Result<i32, &str> = Err("gagal");
// err.unwrap(); // PANIC: "called `Result::unwrap()` on an `Err` value: gagal"
// expect — seperti unwrap tapi dengan pesan panic custom
let ok: Result<i32, &str> = Ok(42);
let nilai = ok.expect("Harusnya berhasil"); // 42
// let err: Result<i32, &str> = Err("gagal");
// err.expect("Operasi kritis gagal!"); // PANIC: "Operasi kritis gagal!: gagal"
// unwrap_or — aman, berikan nilai default
let err: Result<i32, &str> = Err("gagal");
let nilai = err.unwrap_or(0); // 0, TIDAK panic
println!("unwrap_or: {}", nilai);
// unwrap_or_default — nilai default dari tipe
let nama: Option<String> = None;
let nama = nama.unwrap_or_default(); // String::from("")
println!("nama kosong: '{}'", nama);
// Kapan menggunakan unwrap/expect?
// ✅ OK: prototyping, contoh, test
// ✅ OK: ketika Anda YAKIN tidak akan gagal
// ❌ HINDARI: di kode produksi, di library
// ❌ HINDARI: untuk operasi yang bisa gagal secara realistis
// Contoh OK:
let pesan = "42";
let angka: i32 = pesan.parse().expect("pesan harus angka");
// Contoh HARUS dihindari:
// let isi = fs::read_to_string("config.txt").unwrap(); // bisa gagal!
// Lebih baik:
match fs::read_to_string("config.txt") {
Ok(isi) => println!("Config: {}", isi),
Err(e) => eprintln!("Gagal baca config: {}", e),
}
}
Kapan Aman Menggunakan Unwrap
fn main() {
// ✅ Saat Anda TAHU pasti nilainya Ok/Some
let angka: i32 = "42".parse().unwrap(); // Selalu berhasil untuk "42"
// ✅ Dengan validasi sebelumnya
let data = vec![1, 2, 3];
if !data.is_empty() {
let pertama = data.first().unwrap(); // Pasti Some karena tidak kosong
println!("Pertama: {}", pertama);
}
// ✅ Regex compile (pasti valid)
// let re = regex::Regex::new(r"\d+").unwrap();
// ✅ Dalam unit test
#[test]
fn test_parsing() {
let nilai: i32 = "42".parse().unwrap();
assert_eq!(nilai, 42);
}
// Alternatif yang lebih aman daripada unwrap:
let input = "bukan angka";
match input.parse::() {
Ok(n) => println!("Angka: {}", n),
Err(e) => println!("Bukan angka: {}", e),
}
// Atau dengan if let
if let Ok(n) = input.parse::() {
println!("Angka: {}", n);
}
}
5. Option<T> — Nilai Nullable
Option adalah enum bawaan Rust untuk menangani kasus di mana nilai mungkin tidak ada. Ini menggantikan null atau nil dari bahasa lain, dengan cara yang lebih aman.
Definisi dan Penggunaan
// Option sudah didefinisikan di standard library:
// enum Option<T> {
// Some(T), // Ada nilainya
// None, // Tidak ada nilai
// }
fn cari(data: &[i32], target: i32) -> Option<usize> {
for (i, &val) in data.iter().enumerate() {
if val == target {
return Some(i);
}
}
None
}
fn bagi(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
let angka = vec![10, 20, 30, 40, 50];
// match dengan Option
match cari(&angka, 30) {
Some(idx) => println!("Ditemukan di index {}", idx),
None => println!("Tidak ditemukan"),
}
// if let — shorthand
if let Some(idx) = cari(&angka, 40) {
println!("Index 40: {}", idx);
}
// Method pada Option
let hasil = cari(&angka, 30);
println!("is_some: {}", hasil.is_some()); // true
println!("is_none: {}", hasil.is_none()); // false
// map — transformasi inner value
let doubled = hasil.map(|idx| idx * 2);
println!("doubled: {:?}", doubled); // Some(4)
// and_then — chain operasi yang mengembalikan Option
let hasil = cari(&angka, 30)
.and_then(|idx| angka.get(idx + 1)); // ambil elemen berikutnya
println!("berikutnya: {:?}", hasil); // Some(40)
// filter
let x = Some(42);
let filtered = x.filter(|&v| v > 50);
println!("filtered: {:?}", filtered); // None (42 tidak > 50)
// unwrap_or
let tidak_ada = cari(&angka, 99);
println!("unwrap_or: {}", tidak_ada.unwrap_or(0)); // 0
// unwrap_or_else
let nilai = tidak_ada.unwrap_or_else(|| {
println!("Tidak ditemukan, menggunakan default");
-1
});
println!("nilai: {}", nilai);
}
Option dan Result Conversion
fn main() {
// Option → Result
let some_val: Option<i32> = Some(42);
let none_val: Option<i32> = None;
// ok_or: Option → Result (dengan error value)
let result = some_val.ok_or("Tidak ada nilai");
println!("{:?}", result); // Ok(42)
let result = none_val.ok_or("Tidak ada nilai");
println!("{:?}", result); // Err("Tidak ada nilai")
// ok_or_else: dengan closure
let result = none_val.ok_or_else(|| format!("Error: nilai hilang"));
// Result → Option
let ok: Result<i32, &str> = Ok(42);
let err: Result<i32, &str> = Err("gagal");
println!("ok: {:?}", ok.ok()); // Some(42)
println!("err: {:?}", err.ok()); // None
println!("ok: {:?}", ok.err()); // None
println!("err: {:?}", err.err()); // Some("gagal")
// flatten — Option<Option<T>> → Option<T>
let nested: Option<Option<i32>> = Some(Some(42));
let flat: Option<i32> = nested.flatten();
println!("flat: {:?}", flat); // Some(42)
// zip — gabungkan dua Option menjadi tuple
let a = Some(1);
let b = Some("hello");
let zipped = a.zip(b);
println!("zipped: {:?}", zipped); // Some((1, "hello"))
// get_or_insert — untuk mutable Option
let mut x: Option<i32> = None;
x.get_or_insert(42);
println!("x: {:?}", x); // Some(42)
}
6. Operator ? — Error Propagation
Operator ? adalah shorthand yang sangat powerful untuk menyebarkan (propagate) error ke pemanggil fungsi. Ini menggantikan pattern match yang berulang-ulang.
Cara Kerja Operator ?
use std::fs::File;
use std::io::{self, Read};
// TANPA operator ? — verbose dan berulang
fn baca_file_tanpa_tanda(path: &str) -> Result<String, io::Error> {
let file_result = File::open(path);
let mut file = match file_result {
Ok(f) => f,
Err(e) => return Err(e), // return Err secara manual
};
let mut konten = String::new();
match file.read_to_string(&mut konten) {
Ok(_) => Ok(konten),
Err(e) => Err(e), // return Err secara manual
}
}
// DENGAN operator ? — bersih dan ringkas
fn baca_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // Err → return Err(e)
let mut konten = String::new();
file.read_to_string(&mut konten)?; // Err → return Err(e)
Ok(konten)
}
// Bisa di-chain:
fn baca_file_chain(path: &str) -> Result<String, io::Error> {
let mut konten = String::new();
File::open(path)?.read_to_string(&mut konten)?;
Ok(konten)
}
// Lebih singkat lagi:
fn baca_file_singkat(path: &str) -> Result<String, io::Error> {
std::fs::read_to_string(path)
}
fn main() {
match baca_file("data.txt") {
Ok(isi) => println!("Isi: {}", isi),
Err(e) => println!("Error: {}", e),
}
}
Operator ? dengan Berbagai Tipe
// ? pada Option — mengembalikan None jika None
fn kata_pertama(teks: &str) -> Option<&str> {
let pos = teks.find(' ')?; // None jika tidak ada spasi
Some(&teks[..pos])
}
fn ambil_karakter_ke(teks: &str, index: usize) -> Option<char> {
teks.chars().nth(index)? // None jika index di luar batas
}
// ? pada Result — mengembalikan Err jika Err
fn parse_angka_dan_kali(input: &str, faktor: i32) -> Result<i32, std::num::ParseIntError> {
let angka: i32 = input.parse()?; // Err jika gagal parse
Ok(angka * faktor)
}
// Menggabungkan Result dan Option dengan ?
fn cari_angka_pertama(teks: &str) -> Option<i32> {
let digit_str = teks.chars()
.find(|c| c.is_ascii_digit())?;
Some(digit_str.to_digit(10)? as i32)
}
fn main() {
// ? pada Option
let hasil = kata_pertama("Hello Rust World");
println!("Kata pertama: {:?}", hasil); // Some("Hello")
let hasil = kata_pertama("NoSpace");
println!("Kata pertama: {:?}", hasil); // None
// ? pada Result
let hasil = parse_angka_dan_kali("21", 2);
println!("21 × 2 = {:?}", hasil); // Ok(42)
let hasil = parse_angka_dan_kali("abc", 2);
println!("Error: {:?}", hasil); // Err(ParseIntError)
// Gabungan
let hasil = cari_angka_pertama("abc3def");
println!("Angka: {:?}", hasil); // Some(3)
}
Aturan Penggunaan Operator ?
- Hanya bisa digunakan di fungsi yang return type-nya
Result,Option, atau tipe lain yang implementTry ?padaResultmengembalikanErr(e)ke pemanggil?padaOptionmengembalikanNoneke pemanggil- Tidak bisa digunakan di
fn main()kecuali return type-nyaResult
use std::fs;
// main bisa mengembalikan Result!
fn main() -> Result<(), Box<dyn std::error::Error>> {
let isi = fs::read_to_string("config.txt")?; // ? di main!
println!("Config: {}", isi);
let angka: i32 = "42".parse()?; // ? di main!
println!("Angka: {}", angka);
Ok(()) // main mengembalikan Ok jika berhasil
}
// Alternatif: menggunakan anyhow di main
// fn main() -> anyhow::Result<()> { ... }
7. Custom Error Types
Untuk aplikasi dan library yang lebih besar, Anda perlu membuat tipe error custom yang bisa mewakili berbagai jenis error yang mungkin terjadi.
Enum Error
use std::fmt;
use std::io;
use std::num::ParseIntError;
// Definisi custom error enum
#[derive(Debug)]
enum AppError {
IoError(io::Error),
ParseError(ParseIntError),
NotFound(String),
Validation { field: String, message: String },
Custom(String),
}
// Implement Display untuk pesan error yang user-friendly
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::IoError(e) => write!(f, "IO Error: {}", e),
AppError::ParseError(e) => write!(f, "Parse Error: {}", e),
AppError::NotFound(item) => write!(f, "Tidak ditemukan: {}", item),
AppError::Validation { field, message } => {
write!(f, "Validasi gagal pada '{}': {}", field, message)
}
AppError::Custom(msg) => write!(f, "{}", msg),
}
}
}
// Implement Error trait
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
AppError::IoError(e) => Some(e),
AppError::ParseError(e) => Some(e),
_ => None,
}
}
}
fn proses_data(path: &str) -> Result<i32, AppError> {
let konten = std::fs::read_to_string(path)
.map_err(AppError::IoError)?;
let angka: i32 = konten.trim().parse()
.map_err(AppError::ParseError)?;
if angka < 0 {
return Err(AppError::Validation {
field: "angka".to_string(),
message: "Tidak boleh negatif".to_string(),
});
}
Ok(angka)
}
fn main() {
match proses_data("data.txt") {
Ok(n) => println!("Hasil: {}", n),
Err(e) => println!("Error: {}", e),
}
}
Struct Error Sederhana
use std::fmt;
// Struct error sederhana
#[derive(Debug)]
struct ValidationError {
field: String,
message: String,
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error pada '{}': {}", self.field, self.message)
}
}
impl std::error::Error for ValidationError {}
// Helper function untuk membuat error dengan mudah
fn validation_error(field: &str, message: &str) -> ValidationError {
ValidationError {
field: field.to_string(),
message: message.to_string(),
}
}
fn validasi_umur(umur: i32) -> Result<i32, ValidationError> {
if umur < 0 {
Err(validation_error("umur", "Tidak boleh negatif"))
} else if umur > 150 {
Err(validation_error("umur", "Tidak valid (> 150)"))
} else {
Ok(umur)
}
}
fn validasi_nama(nama: &str) -> Result<&str, ValidationError> {
if nama.is_empty() {
Err(validation_error("nama", "Tidak boleh kosong"))
} else if nama.len() < 2 {
Err(validation_error("nama", "Minimal 2 karakter"))
} else {
Ok(nama)
}
}
fn main() {
let nama = validasi_nama("Budi");
let umur = validasi_umur(25);
println!("Nama: {:?}", nama); // Ok("Budi")
println!("Umur: {:?}", umur); // Ok(25)
let umur_invalid = validasi_umur(-5);
println!("Umur invalid: {:?}", umur_invalid);
// Err(ValidationError { field: "umur", message: "Tidak boleh negatif" })
}
8. From Trait dan Konversi Error
Implementasi From trait memungkinkan operator ? mengonversi berbagai tipe error ke tipe error Anda secara otomatis.
use std::io;
use std::num::ParseIntError;
use std::fmt;
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
Custom(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO: {}", e),
AppError::Parse(e) => write!(f, "Parse: {}", e),
AppError::Custom(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for AppError {}
// Implement From — operator ? akan otomatis memanggil ini
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
impl From<ParseIntError> for AppError {
fn from(error: ParseIntError) -> Self {
AppError::Parse(error)
}
}
// Sekarang ? bisa otomatis mengonversi error!
fn proses_file(path: &str) -> Result<i32, AppError> {
let konten = std::fs::read_to_string(path)?; // io::Error → AppError::Io
let angka: i32 = konten.trim().parse()?; // ParseIntError → AppError::Parse
Ok(angka * 2)
}
fn main() {
match proses_file("data.txt") {
Ok(n) => println!("Hasil: {}", n),
Err(AppError::Io(e)) => println!("File error: {}", e),
Err(AppError::Parse(e)) => println!("Parse error: {}", e),
Err(AppError::Custom(msg)) => println!("Custom: {}", msg),
}
}
9. thiserror dan Anyhow
Dua crate populer untuk error handling di Rust adalah thiserror (untuk library) dan anyhow (untuk aplikasi).
thiserror — Library Errors
[dependencies] thiserror = "1" anyhow = "1"
use thiserror::Error;
// thiserror otomatis implement Display dan Error
#[derive(Error, Debug)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("Tidak ditemukan: {0}")]
NotFound(String),
#[error("Validasi gagal pada '{field}': {message}")]
Validation { field: String, message: String },
}
// #[from] otomatis implement From trait!
// Jadi ? langsung bekerja
fn proses_data(path: &str) -> Result<i32, AppError> {
let konten = std::fs::read_to_string(path)?; // otomatis konversi
let angka: i32 = konten.trim().parse()?; // otomatis konversi
if angka < 0 {
return Err(AppError::Validation {
field: "nilai".to_string(),
message: "Tidak boleh negatif".to_string(),
});
}
Ok(angka)
}
anyhow — Aplikasi Errors
use anyhow::{Context, Result, bail, ensure};
// anyhow::Result menggantikan Result<T, Box<dyn Error>>
fn baca_config(path: &str) -> Result<String> {
let isi = std::fs::read_to_string(path)
.with_context(|| format!("Gagal membuka file: {}", path))?;
Ok(isi)
}
fn proses_angka(input: &str) -> Result<i32> {
let angka: i32 = input.parse()
.context("Gagal parsing angka")?;
ensure!(angka >= 0, "Angka harus positif, dapat: {}", angka);
if angka > 1000 {
bail!("Angka terlalu besar: {}", angka);
}
Ok(angka)
}
// Main dengan anyhow::Result
fn main() -> Result<()> {
let config = baca_config("config.txt")?;
println!("Config: {}", config);
let n = proses_angka("42")?;
println!("Angka: {}", n);
Ok(())
}
// anyhow cocok untuk:
// ✅ Aplikasi (bukan library)
// ✅ Prototyping
// ✅ CLI tools
// ✅ Kode yang membutuhkan error handling cepat
// thiserror cocok untuk:
// ✅ Library yang dipublikasikan
// ✅ Kode yang perlu tipe error spesifik
// ✅ API yang perlu dipanggil dengan match
10. Best Practices
Berikut ringkasan best practices untuk error handling di Rust:
| Praktik | Penjelasan |
|---|---|
Gunakan Result | Untuk semua operasi yang bisa gagal di kode produksi |
Hindari unwrap() | Kecuali dalam prototyping atau ketika Anda yakin 100% tidak gagal |
Gunakan ? operator | Untuk propagate error secara ringkas |
| Buat custom error | Untuk library, gunakan enum error yang informatif |
Gunakan thiserror | Untuk mengurangi boilerplate error di library |
Gunakan anyhow | Untuk error handling cepat di aplikasi |
| Tambahkan context | Gunakan .context() untuk menambah info saat propagate |
panic! untuk bug | Hanya untuk kondisi yang mustahil terjadi |
Untuk library: buat custom error enum + implement std::error::Error + Display. Gunakan thiserror untuk mengurangi boilerplate. Untuk aplikasi: gunakan anyhow::Result dan .context() untuk pesan error yang deskriptif.
📝 Quiz Pemahaman Error Handling
Uji pemahaman Anda tentang error handling di Rust!