Tipe Result dan Option#

Result dan Option Type#

Pada Rust, kita tidak melakukan error handling secara konvensional dengan try-catch seperti bahasa lainnya, melainkan menggunakan sebuah tipe yang diambil dari bahasa pemrograman fungsional, yaitu Result type. Untuk Option type, Rust menggunakannya sebagai tipe yang merepresentasikan value yang dapat kosong atau null. Rust menerapkan null safety atau keamanan dari null dengan menggunakan Option.

Result Type#

Pada Rust, Result type diimplementasikan menggunakan enum Deklarasi Result type pada standard library std::result adalah sebagai berikut:

enum Result<T, E> {
   Ok(T),
   Err(E),
}

T dan E diatas merupakan tipe generic dimana kita dapat memasukkan tipe apapun kedalam sana dimana T merupakan tipe dari nilai yang akan dikembalikan dan E merupakan tipe dari error yang dapat muncul. Untuk generics akan kita bahas lebih dalam nanti. Sekarang, kita berfokus terlebih dahulu pada Result type.

Sekarang, kita akan melihat contoh penggunaan Result. Misalnya, kita memiliki file tes.txt dengan isi teks “Halo”, lalu kita ingin membukanya.

use std::fs::File;
use std::io::Read;

fn main() {
    let mut file = match File::open("tes.txt") {
        Ok(f) => f,
        Err(_) => panic!("File tidak dapat dibuka"),
    };
    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => {},
        Err(e) => panic!("Tidak dapat membaca konten {}", e)
    };
    println!("{}", contents);
}

Nah, seperti yang pernah dijelaskan, kita bisa memakai match untuk enum adan karena Result type merupakan enum, kita bisa me-handle error dengan menggunakan match. Ok akan me-wrap atau membungkus value yang ingin kita dapatkan, sedangkan Err akan membungkus value error. Untuk ignore atau mengabaikan variabel yang di-wrap dalam varian, kita dapat menggunakan underscore _. Pada kode diatas, bila file tes.txt ditemukan, maka variabel f yang merupakan tipe File akan di-assign ke file. Sedangkan bila file tidak ditemukan, maka program akan terhenti diakrenakan panic. Untuk baris match file.read_to_string(&mut contents), kita menggunakan underscore didalam Ok dan kemudian tidak mengembalikan apa-apa, kita hanya mengisinya dengan kurung kurawal kosong {} dikarenakan method read_to_string akan mengisi variabel lain sehingga nilai dari read_to_string itu sendiri tidak dibutuhkan. Kita juga dapat menggunakan error yang terjadi dengan memakai variabel yang di-wrap dalam Err seperti e diatas.

Cara diatas sangatlah awkward dan terlalu berputar-putar. Seharusnya kita tidak perlu menggunakan match dan kemudian memasang panic seperti itu. Error handling dengan match harusnya dilakukan disaat kita harus me-handle error dengan lebih detail lagi, seperti mengembalikan value errornya dari fungsi, fallback ketika error terjadi, dan sebagainya. Untuk mengatasi hal seperti diatas, Rust memiliki beberapa method yang lebih praktis.

Unwrap#

unwrap adalah sebuah method Rust dimana kita langsung mengambil variabel yang di-wrap didalam Ok pada Result type dan akan terjadi panic bila Err terjadi. Kita sebaiknya memakai unwrap bila sudah yakin bahwa error tidak akan terjadi dan pasti value Ok yang akan dikembalikan. Namun sebagai contoh, kita akan memakai unwrap untuk kode diatas.

use std::fs::File;
use std::io::Read;

fn main() {
    let mut file = File::open("tes.txt").unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    println!("{}", contents);
}

Nah, kode ini hampir ekuivalen dengan kode diatas. Namun, ada cara method lain yang akan membuat kode sependek menggunakan unwrap, namun ekuivalen dengan kode diatas.

Expect#

Dengan menggunakan method expect, kita dapat menuliskan error kita sendiri bila panic terjadi. expect tidaklah berbeda dengan unwrap, ia akan langsung mengambil value dalam Ok, atau panic. Namun, kita dapat menuliskan error kita sendiri.

use std::fs::File;
use std::io::Read;

fn main() {
    let mut file = File::open("tes.txt").expect("Tidak dapat membuka file");
    let mut contents = String::new();
    file.read_to_string(&mut contents).expect("Tidak dapat membaca file");
    println!("{}", contents);
}

Kode ini ekuivalen dengan kode yang menggunakan match diatas. Bila panic terjadi, maka seperti kita menggunakan macro panic!, pesan yang akan keluar adalah pesan yang kita tuliskan sendiri.

? Operator#

Rust memiliki sebuah operator khusus yang bisa digunakan untuk me-handle error dengan lebih praktis dan aman, yaitu dengan operator ?. Namun, untuk menggunakan operator ?, return type dari sebuah fungsi haruslah Result atau Option type, dan bagi Result, error yang ada pada Result type yang dikembalikan harus mengimplementasikan From<T> yang akan kita bahas lebih lanjut nanti.

use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    let mut file = File::open("tes.txt")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    println!("{}", contents);

    // Agar sesuai return type
    Ok(())
}

Kode diatas merupakan contoh penggunaan ? operator. ? operator akan mengembalikan Err bila error terjadi, dan akan me-assign value yang berada dalam Ok kepada variabel yang bersangkutan dengan operator ?. Jadi ia berfungsi sama seperti semua method diatas, namun tidak panic bila error terjadi, tapi mengembalikan error tersebut. Untuk assignment value, ia akan assign seperti biasa bila sukses sehingga membuatnya ekuivalen dengan hal seperti ini:

let mut file = match File::open("tes.txt") {
    Ok(f) => f,
    Err(e) => return Err(e),
};

Option Type#

Option type adalah enum, yang digunakan untuk null-safety dimana kita memakainya bila suatu value dapat memiliki kemungkinan untuk kosong. Definisi Option adalah sebagai berikut:

pub enum Option<T> {
    None,
    Some(T),
}

Varian Some(T) merupakan varian yang berisi value bila value tersebut ada, dimana T adalah tipe generic yang mana value apapun dapat masuk kesana sedangkan None merupakan varian bila value tersebut kosong. Namun dengan Option type, value tersebut bukanlah null, tapi enum Option tersebut.

Penggunaan Option type juga sama seperti Result type. Kita dapat menggunakan match, unwrap, expect, maupun operator ? untuk me-handle None.

fn count_word(sentence: String, word: &str) -> Option<i32> {
    let mut count = 0;
    let vec = sentence.split(" ").map(|s| s.to_string()).collect::<Vec<String>>();

    for i in vec {
        if i == word {
            count += 1;
        }
    }
    if count == 0 {
        None
    }
    else {
        Some(count)
    }
}

fn main() {
    let sentence = String::from("Aku sedang coding bahasa Rust dan bahasa Rust ini sangat keren");
    let count = count_word(sentence, "Rust");
    match count {
        None => println!("Tidak ditemukan kata tersebut di kalimat tersebut"),
        Some(value) => println!("Ditemukan {} kata tersebut di kalimat tersebut", value)
    };
}

Kode diatas merupakan sebuah contoh penggunaan Option type dimana count_word merupakan fungsi yang menghitung jumlah kata tertentu didalam sebuah kalimat. Bila kata ditemukan, maka jumlah kata akan dikembalikan dalam varian Some dan bila tidak, varian None akan dikembalikan dari fungsi count_word. Lalu pada fungsi main, tergantung pada kondisi apakah varian yang dikembalikan adalah None atau Some, output Tidak ditemukan kata tersebut di kalimat tersebut atau Ditemukan <jumlah kata> kata tersebut di kalimat tersebut akan dicetak.

Unwrap Or#

unwrap_or merupakan sebuah method untuk melakukan unwrap terhadap Option type, namun tidak akan panic bila value yang di-unwrap ternyata None, melainkan akan lari ke value default yang didefinisikan.

fn default_example(opt: Option<&str>) {
    println!("{}", opt.unwrap_or("Ini adalah value default"));
}

fn main() {
    default_example(Some("Test"));
    default_example(None);
}

# Latihan

TODO