Error Handling, Traits, dan Generic di Rust: Panduan Lanjutan
Di artikel sebelumnya kita sudah memahami ownership, borrowing, dan lifetimes. Sekarang saatnya belajar tiga konsep penting lainnya: error handling, traits, dan generic.
Ketiga konsep ini saling berkaitan dan sangat penting untuk menulis kode Rust yang bersih, aman, dan reusable.
Error Handling
Rust punya pendekatan unik untuk menangani error. Tidak ada exception seperti di Java atau Python. Sebagai gantinya, Rust menggunakan tipe data khusus yang memaksa programmer menangani setiap kemungkinan error.
Panic vs Result
Rust membedakan dua jenis error:
- Unrecoverable error: Program tidak bisa melanjutkan. Gunakan
panic! - Recoverable error: Error yang bisa ditangani. Gunakan
Result
fn main() {
// Unrecoverable: program berhenti
// panic!("Sesuatu yang sangat salah terjadi!");
// Recoverable: bisa ditangani
let result: Result<i32, String> = Ok(42);
println!("{:?}", result);
}
Jangan gunakan panic! untuk error biasa. Gunakan hanya untuk kondisi yang benar-benar tidak bisa dipulihkan.
Result Type
Result adalah enum dengan dua variant:
enum Result<T, E> {
Ok(T), // Berhasil, berisi nilai
Err(E), // Gagal, berisi error
}
Contoh Penggunaan
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Tidak bisa bagi dengan nol!"))
} else {
Ok(a / b)
}
}
fn main() {
let result = divide(10.0, 3.0);
println!("{:?}", result); // Ok(3.3333...)
let result = divide(10.0, 0.0);
println!("{:?}", result); // Err("Tidak bisa bagi dengan nol!")
}
Match dengan Result
fn main() {
let result = divide(10.0, 3.0);
match result {
Ok(value) => println!("Hasil: {:.2}", value),
Err(e) => println!("Error: {}", e),
}
}
Operator ?
Operator ? adalah shorthand untuk menangani Result. Jika Ok, ambil nilai-nya. Jika Err, return error dari function:
fn read_file(path: &str) -> Result<String, std::io::Error> {
let content = std::fs::read_to_string(path)?; // ? disini
Ok(content)
}
// Tanpa ?, kodenya seperti ini:
fn read_file_verbose(path: &str) -> Result<String, std::io::Error> {
match std::fs::read_to_string(path) {
Ok(content) => Ok(content),
Err(e) => return Err(e),
}
}
Operator ? hanya bisa digunakan di function yang mengembalikan Result atau Option.
unwrap dan expect
Jika kamu yakin tidak akan ada error:
fn main() {
// unwrap: panic jika Err
let value = divide(10.0, 3.0).unwrap();
println!("{}", value);
// expect: panic dengan pesan custom
let value = divide(10.0, 3.0).expect("Harusnya tidak error");
println!("{}", value);
}
Hati-hati: unwrap dan expect akan panic jika error. Gunakan hanya untuk prototyping atau ketika kamu 100
Option Type
Option digunakan untuk nilai yang bisa ada atau tidak ada (null safety):
enum Option<T> {
Some(T), // Ada nilainya
None, // Tidak ada
}
Rust tidak punya null seperti bahasa lain. Sebagai gantinya, gunakan Option.
Contoh
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("ERLKIM"))
} else {
None
}
}
fn main() {
let user = find_user(1);
match user {
Some(name) => println!("User ditemukan: {}", name),
None => println!("User tidak ditemukan"),
}
let user = find_user(99);
// unwrap_or: gunakan default jika None
let name = user.unwrap_or(String::from("Guest"));
println!("Nama: {}", name);
}
Method Berguna untuk Option
fn main() {
let x: Option<i32> = Some(5);
let y: Option<i32> = None;
// unwrap_or: default jika None
println!("{}", x.unwrap_or(0)); // 5
println!("{}", y.unwrap_or(0)); // 0
// map: transformasi nilai di dalam Some
let doubled = x.map(|n| n * 2); // Some(10)
println!("{:?}", doubled);
// is_some dan is_none
println!("{}", x.is_some()); // true
println!("{}", y.is_none()); // true
// and_then: chain operasi yang bisa gagal
let result = x.and_then(|n| if n > 0 { Some(n * 10) } else { None });
println!("{:?}", result); // Some(50)
}
Menggabungkan Option dan Result
Kadang kamu perlu mengonversi antara Option dan Result:
fn main() {
let opt: Option<i32> = Some(5);
// Option ke Result
let res: Result<i32, String> = opt.ok_or(String::from("Value tidak ada"));
println!("{:?}", res); // Ok(5)
// Result ke Option
let res: Result<i32, String> = Ok(42);
let opt: Option<i32> = res.ok();
println!("{:?}", opt); // Some(42)
}
Custom Error Type
Untuk project yang lebih besar, buat custom error type:
use std::fmt;
#[derive(Debug)]
enum AppError {
NotFound(String),
ParseError(String),
IoError(std::io::Error),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
AppError::ParseError(msg) => write!(f, "Parse error: {}", msg),
AppError::IoError(err) => write!(f, "IO error: {}", err),
}
}
}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
AppError::IoError(err)
}
}
fn read_config(path: &str) -> Result<String, AppError> {
let content = std::fs::read_to_string(path)?; // Auto convert ke AppError
if content.is_empty() {
return Err(AppError::ParseError(String::from("Config kosong")));
}
Ok(content)
}
fn main() {
match read_config("config.toml") {
Ok(config) => println!("Config: {}", config),
Err(e) => println!("Error: {}", e),
}
}
Dengan From trait, operator ? bisa otomatis mengonversi error type. Ini sangat memudahkan chaining operasi yang bisa gagal.
Traits
Trait di Rust mirip dengan interface di bahasa lain. Trait mendefinisikan perilaku yang bisa dimiliki oleh tipe data. Ini adalah cara Rust untuk polymorphism.
Mendefinisikan Trait
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
author: String,
content: String,
}
struct Tweet {
username: String,
text: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} oleh {} - {}", self.title, self.author, &self.content[..50])
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.text)
}
}
fn main() {
let article = Article {
title: String::from("Belajar Rust"),
author: String::from("ERLKIM"),
content: String::from("Rust adalah bahasa yang aman dan cepat..."),
};
let tweet = Tweet {
username: String::from("erlkim"),
text: String::from("Rust is awesome!"),
};
println!("{}", article.summarize());
println!("{}", tweet.summarize());
}
Default Method
Trait bisa punya method dengan implementasi default:
trait Summary {
fn summarize_author(&self) -> String;
// Default implementation
fn summarize(&self) -> String {
format!("(Baca lebih lanjut dari {}...)", self.summarize_author())
}
}
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
// summarize() menggunakan default
}
fn main() {
let article = Article {
title: String::from("Rust"),
author: String::from("ERLKIM"),
};
println!("{}", article.summarize()); // (Baca lebih lanjut dari ERLKIM...)
}
Trait Bounds
Trait bisa digunakan sebagai batasan untuk generic:
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// Atau dengan syntax lebih eksplisit:
fn notify_explicit<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
// Multiple trait bounds:
fn display<T: Summary + std::fmt::Display>(item: &T) {
println!("{}", item);
}
Trait sebagai Return Type
fn create_summary() -> Box<dyn Summary> {
Box::new(Article {
title: String::from("Rust"),
author: String::from("ERLKIM"),
content: String::from("..."),
})
}
dyn Summary artinya tipe yang mengimplementasikan Summary, ditentukan di runtime (dynamic dispatch).
~Erlkim
Artikel Terkait

Collections, Iterators, dan Closures di Rust: Panduan Praktis
Menguasai collections, iterators, dan closures di Rust. Vec, HashMap, String, dan cara memproses data dengan efisien dan elegan.

Concurrency dan Async Programming di Rust: Panduan Lengkap
Belajar concurrency dan async programming di Rust. Thread, mutex, channel, dan async/await untuk kode yang cepat dan aman.

Ownership, Borrowing, dan Lifetimes di Rust: Panduan Lengkap
Memahami konsep paling penting di Rust: ownership, borrowing, dan lifetimes. Tanpa garbage collector, tanpa memory leak.
Komentar