Penanganan Error di Rust 2026: Result, Option, thiserror dan anyhow
Pelajari cara menangani error di Rust dengan Result, Option, operator ?, thiserror dan anyhow. Panduan lengkap penanganan error Rust untuk developer Indonesia.

Penanganan error di Rust berpusat pada dua enum—Result<T, E> dan Option<T>—yang memaksa penanganan eksplisit atas keberhasilan, kegagalan, dan ketiadaan nilai pada waktu kompilasi. Berbeda dengan exception yang menyebar secara tidak terlihat, pendekatan Rust membuat jalur error terlihat dalam signature fungsi, menghilangkan seluruh kategori kejutan runtime. Operator ?, dikombinasikan dengan crate seperti thiserror dan anyhow, menyederhanakan model eksplisit ini tanpa mengorbankan kejelasan.
Gunakan Option<T> untuk nilai yang mungkin sah tidak ada (field konfigurasi, hasil pencarian). Gunakan Result<T, E> ketika operasi dapat gagal dengan informasi error yang bermakna (I/O file, request jaringan, parsing).
Memahami Dasar-dasar Result dan Option
Result<T, E> merepresentasikan keberhasilan (Ok(T)) atau kegagalan (Err(E)). Option<T> merepresentasikan adanya nilai (Some(T)) atau ketiadaan (None). Keduanya adalah sum types—compiler memastikan setiap varian ditangani.
// Demonstrates Result and Option basic patterns
fn find_user(id: u64) -> Option<String> {
// Returns None if user doesn't exist
if id == 0 {
None
} else {
Some(format!("User-{}", id))
}
}
fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
// Returns Err if parsing fails
s.parse::<u16>()
}
fn main() {
// Option handling - must address None case
match find_user(42) {
Some(name) => println!("Found: {}", name),
None => println!("User not found"),
}
// Result handling - must address Err case
match parse_port("8080") {
Ok(port) => println!("Port: {}", port),
Err(e) => println!("Invalid port: {}", e),
}
}Compiler menolak kode yang mengabaikan nilai return ini tanpa penanganan eksplisit. Desain ini menangkap bug pada waktu kompilasi yang akan muncul sebagai null pointer exception atau uncaught error di bahasa lain.
Operator Tanda Tanya untuk Propagasi yang Ringkas
Operator ? mengubah chain match yang verbose menjadi kode linear yang mudah dibaca. Ketika diterapkan pada Result, operator ini return early dengan error jika ada, atau unwrap nilai sukses. Hal yang sama berlaku untuk Option.
// Using ? for clean error propagation
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn read_first_line(path: &str) -> Result<String, io::Error> {
let file = File::open(path)?; // Returns early if open fails
let mut reader = BufReader::new(file);
let mut line = String::new();
reader.read_line(&mut line)?; // Returns early if read fails
Ok(line.trim().to_string())
}
fn get_port_from_config(path: &str) -> Result<u16, Box<dyn std::error::Error>> {
let content = read_first_line(path)?;
let port = content.parse::<u16>()?; // ParseIntError converts via From
Ok(port)
}Operator ? memerlukan tipe error yang dapat dikonversi ke tipe error return fungsi melalui trait From. Menggunakan Box<dyn std::error::Error> seperti ditunjukkan di atas menerima tipe error apa pun yang mengimplementasikan trait Error standar.
Membuat Custom Error dengan thiserror
Crate thiserror menghilangkan boilerplate untuk tipe error kustom. Crate ini menghasilkan implementasi Error, Display, dan From melalui procedural macro.
// Custom error types using thiserror 2.0
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("configuration file not found at {path}")]
NotFound { path: String },
#[error("invalid port number: {0}")]
InvalidPort(#[from] std::num::ParseIntError),
#[error("IO error reading config")]
IoError(#[from] std::io::Error),
#[error("missing required field: {0}")]
MissingField(String),
}
fn load_config(path: &str) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)?; // IoError auto-converts
let port: u16 = content
.lines()
.find(|l| l.starts_with("port="))
.ok_or(ConfigError::MissingField("port".into()))?
.strip_prefix("port=")
.unwrap()
.parse()?; // ParseIntError auto-converts to InvalidPort
Ok(Config { port })
}
struct Config {
port: u16,
}Atribut #[from] menghasilkan konversi otomatis, memungkinkan penggunaan ? yang mulus dengan berbagai tipe error yang mendasari. Pesan error menjadi self-documenting melalui format string #[error(...)].
Error Level Aplikasi dengan anyhow
Sementara thiserror cocok untuk kode library dengan tipe error spesifik, anyhow menargetkan aplikasi di mana konteks error lebih penting daripada granularitas tipe. Trait Context-nya menambahkan pesan deskriptif ke error apa pun.
// Application error handling with anyhow 1.0
use anyhow::{Context, Result, bail, ensure};
fn load_database_url() -> Result<String> {
std::env::var("DATABASE_URL")
.context("DATABASE_URL environment variable not set")
}
fn connect_to_database(url: &str) -> Result<DatabaseConnection> {
ensure!(!url.is_empty(), "database URL cannot be empty");
let conn = DatabaseConnection::new(url)
.context("failed to establish database connection")?;
if !conn.is_healthy() {
bail!("database connection unhealthy after establishment");
}
Ok(conn)
}
fn main() -> Result<()> {
let url = load_database_url()?;
let conn = connect_to_database(&url)
.context("application startup failed")?;
// Context chains create readable error traces:
// Error: application startup failed
// Caused by:
// 0: failed to establish database connection
// 1: connection refused
Ok(())
}
struct DatabaseConnection;
impl DatabaseConnection {
fn new(_url: &str) -> Result<Self> { Ok(Self) }
fn is_healthy(&self) -> bool { true }
}Method context() membungkus error dengan informasi tambahan, menciptakan chain yang membantu debugging. Macro bail! menyediakan early exit dengan pesan error terformat, sementara ensure! bertindak sebagai assertion yang mengembalikan error alih-alih panic.
Siap menguasai wawancara Rust Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Menggabungkan thiserror dan anyhow dalam Proyek Nyata
Library mengekspos error terstruktur via thiserror untuk penanganan programatik oleh konsumen. Aplikasi membungkus error tersebut dengan anyhow untuk output yang mudah dibaca manusia. Pemisahan ini menjaga API tetap bersih sambil mempertahankan kemampuan debugging.
// Exposes typed errors for programmatic handling
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PaymentError {
#[error("insufficient funds: required {required}, available {available}")]
InsufficientFunds { required: u64, available: u64 },
#[error("card declined: {reason}")]
CardDeclined { reason: String },
#[error("payment provider unavailable")]
ProviderUnavailable(#[source] reqwest::Error),
}
pub fn process_payment(amount: u64) -> Result<Receipt, PaymentError> {
// Library returns specific, matchable error types
Err(PaymentError::InsufficientFunds {
required: amount,
available: 50,
})
}
pub struct Receipt;// Wraps library errors with context
use anyhow::{Context, Result};
use my_payment_lib::{process_payment, PaymentError};
fn checkout(cart_total: u64) -> Result<()> {
match process_payment(cart_total) {
Ok(_receipt) => Ok(()),
Err(PaymentError::InsufficientFunds { required, available }) => {
// Handle specific case differently
println!("Add {} to your balance", required - available);
Ok(())
}
Err(e) => Err(e).context("checkout payment processing failed"),
}
}Pola ini memungkinkan pemanggil untuk melakukan match pada varian spesifik ketika pemulihan dimungkinkan, sambil tetap mendapatkan manfaat dari konteks error yang kaya saat mempropagasi kegagalan ke atas. Komunitas Rust sebagian besar telah menstandardisasi pendekatan ini, seperti dibahas dalam panduan API Rust.
Pola Penanganan Error untuk Kode Async
Fungsi async mengembalikan Result sama seperti yang synchronous. Operator ? bekerja identik dalam blok async, dan baik thiserror maupun anyhow terintegrasi tanpa modifikasi.
// Error handling in async Rust with Tokio
use anyhow::{Context, Result};
use std::time::Duration;
async fn fetch_user_data(user_id: u64) -> Result<UserData> {
let response = reqwest::get(format!("https://api.example.com/users/{}", user_id))
.await
.context("HTTP request to user API failed")?;
let status = response.status();
if !status.is_success() {
anyhow::bail!("user API returned status {}", status);
}
let data: UserData = response
.json()
.await
.context("failed to parse user data JSON")?;
Ok(data)
}
async fn fetch_with_retry(user_id: u64, attempts: u32) -> Result<UserData> {
let mut last_error = None;
for attempt in 1..=attempts {
match fetch_user_data(user_id).await {
Ok(data) => return Ok(data),
Err(e) => {
last_error = Some(e);
if attempt < attempts {
tokio::time::sleep(Duration::from_millis(100 * attempt as u64)).await;
}
}
}
}
Err(last_error.unwrap()).context(format!("failed after {} attempts", attempts))
}
#[derive(serde::Deserialize)]
struct UserData {
name: String,
}Ketika menggabungkan beberapa operasi async, gunakan try_join! dari tokio atau futures untuk menjalankannya secara bersamaan sambil mempropagasi error pertama. Untuk konsep terkait, eksplorasi modul pertanyaan interview async/await.
Downcasting dan Inspeksi Error
Baik anyhow::Error maupun Box<dyn Error> mendukung downcasting untuk memulihkan tipe error asli. Ini memungkinkan logging detail spesifik sambil tetap mempropagasi error generik.
// Inspecting wrapped error types
use anyhow::{Context, Result};
use std::io;
fn log_and_propagate(result: Result<()>) -> Result<()> {
if let Err(ref e) = result {
// Check if the root cause is a specific type
if let Some(io_err) = e.downcast_ref::<io::Error>() {
match io_err.kind() {
io::ErrorKind::NotFound => {
tracing::warn!("file not found, using defaults");
}
io::ErrorKind::PermissionDenied => {
tracing::error!("permission denied - check file ownership");
}
_ => {
tracing::error!("IO error: {:?}", io_err);
}
}
}
}
result
}Downcasting menjembatani kesenjangan antara penanganan error generik dan logika pemulihan spesifik. Gunakan secara hemat—jika downcasting sering terjadi, pertimbangkan apakah enum error yang typed akan lebih baik.
Konversi Antara Option dan Result
Standard library menyediakan method untuk mengkonversi antara Option dan Result, memungkinkan komposisi yang mulus ketika API berbeda menggunakan pola berbeda.
// Option and Result interoperability
fn get_env_port() -> Option<u16> {
std::env::var("PORT")
.ok() // Result -> Option (discards error)
.and_then(|s| s.parse().ok())
}
fn get_env_port_with_error() -> Result<u16, String> {
std::env::var("PORT")
.map_err(|_| "PORT not set".to_string())?
.parse()
.map_err(|_| "PORT is not a valid number".to_string())
}
fn lookup_and_parse(map: &std::collections::HashMap<String, String>, key: &str) -> Result<u16, String> {
map.get(key)
.ok_or_else(|| format!("key '{}' not found", key))? // Option -> Result
.parse()
.map_err(|e| format!("parse error for '{}': {}", key, e))
}Method ok() membuang detail error ketika hanya keberadaan yang penting. Method ok_or() dan ok_or_else() mengkonversi None menjadi custom error, memungkinkan propagasi ? dari nilai Option. Pola-pola ini sering muncul dalam pertanyaan interview pattern matching.
Pertimbangan Performa
Penanganan error di Rust tidak membawa biaya runtime pada jalur sukses. Result dan Option adalah enum yang dialokasi di stack dengan ukuran deterministik. Compiler mengoptimasi pengecekan ketika dapat membuktikan cabang tidak dapat dijangkau.
| Pendekatan | Biaya Jalur Sukses | Biaya Jalur Gagal | |------------|-------------------|-------------------| | Result/Option | Nol | Stack unwinding (murah) | | panic! | Nol | Full stack unwinding + cleanup | | Exception C++ | Nol (biasanya) | Alokasi heap mahal + RTTI |
Hindari unwrap() dan expect() dalam kode library—simpan untuk kasus di mana kegagalan benar-benar mengindikasikan bug. Untuk jalur kritis performa di mana error sering terjadi, pertimbangkan menggunakan enum dengan data inline daripada tipe error yang dialokasi di heap.
Kesimpulan
Result<T, E>menangani kegagalan yang dapat dipulihkan;Option<T>menangani ketiadaan—keduanya memaksa penanganan waktu kompilasi- Operator
?mempropagasi error secara ringkas, memerlukan implementasi traitFromuntuk konversi tipe thiserrormenghasilkan tipe error terstruktur untuk library tanpa boilerplateanyhowmenyediakan chain error kontekstual untuk aplikasi, mendukungcontext(),bail!, danensure!- Kombinasikan keduanya: library mengekspos error typed via
thiserror, aplikasi membungkusnya dengananyhow - Kode async menggunakan pola identik—
?bekerja dalam blok async tanpa modifikasi - Gunakan
ok_or()untuk mengkonversiOptionkeResult; gunakanok()untuk sebaliknya - Downcast wrapped error hanya ketika logika pemulihan spesifik memerlukan tipe asli
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Tag
Bagikan
Artikel terkait

Rust 2026: Traits, Generics, dan Pertanyaan Wawancara Tingkat Lanjut
Panduan mendalam tentang traits dan generics Rust dengan fitur terbaru edisi 2024: trait upcasting, AsyncFn, RPITIT, serta pertanyaan wawancara teknis yang sering diujikan.

Smart Pointer Rust Dijelaskan: Box, Rc, Arc, dan RefCell di 2026
Smart pointer Rust Box, Rc, Arc, dan RefCell dijelaskan dengan contoh 2026 yang dapat dikompilasi, tabel keputusan, dan pertanyaan wawancara yang umum.

Async/Await di Rust: Tokio, Futures, dan Concurrency Asinkron Dijelaskan Secara Lengkap
Panduan lengkap tentang pemrograman asinkron di Rust menggunakan async/await, runtime Tokio, dan trait Future. Artikel ini membahas concurrency, channel, error handling, dan pola-pola lanjutan untuk membangun aplikasi Rust yang efisien.