Python

⚠️ Error Handling di Rust

Pelajari cara menangani error dengan elegan di Rust — Result, Option, unwrap, custom errors, ? operator, dan best practices

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
UnrecoverableBug programmer, data corruptProgram berhenti (panic)panic!
RecoverableFile tidak ditemukan, jaringan putusDitangani oleh programmerResult<T, E>
Nilai KosongElemen tidak ada, pencarian gagalDitangani dengan OptionOption<T>
Diagram: Strategi Error Handling Rust
┌─────────────────────────────────────────────────────────────┐
│                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!

Rust — 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

Terminal — Backtrace
# 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

TOML — Cargo.toml
# 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
⚠️ Kapan Harus Panic?
  • 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

Rust — Result Definition
// 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

Rust — Result Methods
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.

Rust — Unwrap & Expect
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

Rust — Safe 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

Rust — Option
// 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

Rust — Option ↔ Result
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 ?

Rust — 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

Rust — ? dengan Tipe Berbeda
// ? 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 ?

⚠️ Aturan Operator ?
  • Hanya bisa digunakan di fungsi yang return type-nya Result, Option, atau tipe lain yang implement Try
  • ? pada Result mengembalikan Err(e) ke pemanggil
  • ? pada Option mengembalikan None ke pemanggil
  • Tidak bisa digunakan di fn main() kecuali return type-nya Result
Rust — main dengan Result
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

Rust — Custom Error Enum
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

Rust — Struct Error
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.

Rust — From Trait
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

TOML — Cargo.toml
[dependencies]
thiserror = "1"
anyhow = "1"
Rust — thiserror
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

Rust — anyhow
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 ResultUntuk semua operasi yang bisa gagal di kode produksi
Hindari unwrap()Kecuali dalam prototyping atau ketika Anda yakin 100% tidak gagal
Gunakan ? operatorUntuk propagate error secara ringkas
Buat custom errorUntuk library, gunakan enum error yang informatif
Gunakan thiserrorUntuk mengurangi boilerplate error di library
Gunakan anyhowUntuk error handling cepat di aplikasi
Tambahkan contextGunakan .context() untuk menambah info saat propagate
panic! untuk bugHanya untuk kondisi yang mustahil terjadi
💡 Ringkasan Strategi

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!

1. Apa perbedaan utama antara panic! dan Result?

a) Tidak ada perbedaan, keduanya sama
b) panic! untuk error recoverable, Result untuk unrecoverable
c) panic! crash program, Result memungkinkan penanganan error
d) Result lebih cepat dari panic!

2. Apa yang dilakukan unwrap() jika nilai adalah Err?

a) Mengembalikan nilai default
b) Mengabaikan error
c) Program panic (crash)
d) Mengembalikan None

3. Apa fungsi operator ? pada Result?

a) Mengonversi Result ke Option
b) Mengabaikan error
c) Meng-propagate error ke pemanggil fungsi
d) Menjalankan unwrap secara otomatis

4. Kapan sebaiknya menggunakan panic!?

a) Untuk semua error di program
b) Untuk input pengguna yang salah
c) Untuk kondisi yang mustahil terjadi dan bug programmer
d) Sebaiknya tidak pernah digunakan

5. Crate mana yang cocok untuk error handling di library Rust?

a) anyhow
b) thiserror
c) serde
d) tokio
🔍 Zoom
100%
🎨 Tema