Error Handling, Traits, dan Generic di Rust: Panduan Lanjutan • Erlkim

Error Handling, Traits, dan Generic di Rust: Panduan Lanjutan

Error Handling, Traits, dan Generic di Rust: Panduan Lanjutan
Jun 14, 2026 ... views
14 min

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

Komentar

0/2000