Belajar Rust - Error Handling dan Result
Episode 4 of 19

Belajar Rust - Error Handling dan Result

Episode ini membedah error handling di Rust: tipe Result dan Option sebagai nilai error-safe, match untuk menangani hasil, operator ? untuk propagasi error yang ringkas, serta pembentukan custom error dengan thiserror dan anyhow untuk aplikasi production.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Hampir semua program di dunia nyata bisa gagal: file tidak ditemukan, koneksi terputus, atau format data salah. Di bahasa lain, error sering ditangani lewat exception yang melompat keluar dari alur normal. Rust mengambil pendekatan berbeda: error adalah nilai, direpresentasikan dengan tipe Result dan Option.

Episode 4 membedah kedua tipe ini, pola match untuk menanganinya, operator ? untuk propagasi error yang ringkas, serta dua crate paling populer untuk custom error: thiserror untuk library dan anyhow untuk aplikasi. Setelah episode ini, kalian akan membaca pesan error Rust dengan kepala dingin.

Result dan Option

Dua Tipe Error-Safe

Result<T, E> menyatakan operasi yang bisa berhasil atau gagal: varian Ok(T) membawa nilai sukses, varian Err(E) membawa error. Option<T> menyatakan nilai yang mungkin tidak ada: Some(T) atau None. Keduanya adalah enum biasa — tidak ada sihir, hanya disiplin tipe.

Membaca file
cat > src/main.rs <<'EOF'
use std::fs::File;
 
fn main() {
    let hasil = File::open("tidak-ada.txt");
 
    match hasil {
        Ok(file) => println!("file terbuka: {:?}", file),
        Err(e) => println!("gagal membuka file: {}", e),
    }
}
EOF
cargo run

File::open mengembalikan Result<File, io::Error>. match hasil memaksa kalian menangani kedua kasus. Di sinilah kekuatan Rust: kemungkinan gagal tidak bisa diabaikan begitu saja.

Option untuk Nilai yang Mungkin Absen

Option menjawab pertanyaan "apakah nilainya ada?". Contoh paling umum: mencari elemen di koleksi.

Option di koleksi
cat > src/main.rs <<'EOF'
fn main() {
    let angka = vec![10, 20, 30];
    let pertama = angka.first();
    let kosong: Vec<i32> = Vec::new();
    let dari_kosong = kosong.first();
 
    println!("pertama: {:?}", pertama);
    println!("dari_kosong: {:?}", dari_kosong);
}
EOF
cargo run

vec.first() mengembalikan Option<&i32>: Some jika ada elemen, None jika vektor kosong. Dengan menggabungkan Option dan Result, Rust menangani ketidakhadiran nilai tanpa null.

match dan Kombinator

match untuk Menangani Kedua Kasus

Pola match pada episode 3 adalah cara dasar menangani Result dan Option. Untuk logika yang hanya peduli pada kasus sukses, kombinator seperti unwrap_or, map, dan ok_or membuat kode lebih padat:

Kombinator
cat > src/main.rs <<'EOF'
fn main() {
    let input = "42";
 
    let angka: Result<i32, _> = input.parse();
    let nilai = angka.unwrap_or(0);
 
    let opsional = Some(7);
    let tambah = opsional.map(|n| n + 1);
 
    println!("nilai: {}, tambah: {:?}", nilai, tambah);
}
EOF
cargo run

unwrap_or(0) memakai nilai fallback jika Err. map mengubah isi Some tanpa menyentuh None. Hindari unwrap dan expect pada kode production karena keduanya memicu panic saat Err atau None.

Operator ? dan Propagasi Error

Menyebar Error ke Pemanggil

Menulis match untuk setiap panggilan yang bisa gagal membuat kode berantakan. Operator ? memendekkannya: jika Err, fungsi mengembalikan error tersebut langsung; jika Ok, isinya diteruskan ke variabel.

Propagasi dengan ?
cat > src/main.rs <<'EOF'
use std::fs;
use std::io;
 
fn baca_panjang(path: &str) -> Result<usize, io::Error> {
    let konten = fs::read_to_string(path)?;
    Ok(konten.len())
}
 
fn main() -> Result<(), io::Error> {
    let panjang = baca_panjang("Cargo.toml")?;
    println!("panjang Cargo.toml: {}", panjang);
    Ok(())
}
EOF
cargo run

fs::read_to_string(path)? mengembalikan error ke pemanggil jika gagal. Perhatikan fn main() -> Result<(), io::Error>: main sendiri kini bisa mengembalikan error, dan runtime mencetaknya jika terjadi.

Konversi Error dengan From

Operator ? melakukan konversi otomatis lewat trait From: jika fungsi mengembalikan io::Error tetapi sumbernya ParseIntError, Rust memanggil konversi yang sesuai. Inilah dasar ekosistem error yang saling terhubung.

Custom Error dengan thiserror dan anyhow

thiserror untuk Library

thiserror menghasilkan implementasi Display dan std::error::Error dari definisi enum — sempurna untuk library yang harus memberi error terstruktur:

Custom error enum
cat > src/main.rs <<'EOF'
use std::fs;
 
#[derive(thiserror::Error, Debug)]
enum AppError {
    #[error("file tidak ditemukan: {0}")]
    NotFound(String),
    #[error("gagal membaca: {0}")]
    Io(#[from] std::io::Error),
}
 
fn baca(path: &str) -> Result<String, AppError> {
    let konten = fs::read_to_string(path)?;
    Ok(konten)
}
 
fn main() -> Result<(), AppError> {
    let isi = baca("Cargo.toml")?;
    println!("{}", isi.lines().next().unwrap_or_default());
    Ok(())
}
EOF
cargo run

#[error("file tidak ditemukan: {0}")] mendefinisikan pesan, dan #[from] menambahkan konversi otomatis. cargo add thiserror menambahkan dependency ke Cargo.toml. Dengan #[from], operator ? langsung mengonversi io::Error menjadi AppError.

anyhow untuk Aplikasi

Untuk binary dan aplikasi yang tidak butuh error terstruktur, anyhow menyediakan Result<T, anyhow::Error> dengan konteks string:

Context dengan anyhow
cat > src/main.rs <<'EOF'
use anyhow::{Context, Result};
 
fn main() -> Result<()> {
    let data = std::fs::read_to_string("config.toml")
        .context("gagal membaca config.toml")?;
    println!("{}", data.lines().count());
    Ok(())
}
EOF
cargo run

context menambahkan pesan kontekstual ke error. Kombinasi idiomatis di industri: thiserror untuk library, anyhow untuk aplikasi dan binary.

Penutup

Inti yang harus dibawa pulang:

  • Result<T, E> untuk operasi yang bisa gagal; Option<T> untuk nilai yang bisa absen.
  • match memaksa kedua kasus ditangani; kombinator seperti unwrap_or dan map memadatkan logika.
  • Operator ? mempropagasi error dan mengonversi tipe lewat trait From.
  • fn main() -> Result<(), E> membiarkan error bocor ke runtime.
  • thiserror untuk custom error terstruktur di library.
  • anyhow dengan context untuk error aplikasi yang kaya informasi.

Di episode 5 selanjutnya kita akan membahas struct, enum, trait, dan generic — definisi data dengan struct dan tuple struct, enum sebagai algebraic data types, trait sebagai interface dengan default impl, serta generic constraints untuk kode yang reusable. Kalian akan mulai membangun tipe data yang mencerminkan domain bisnis.

Belajar Rust - Error Handling dan Result | Belajar Rust