Rust101: Menggunakan Match Sebagai Control Flow yang Powerful

"Sebenarnya ada apa dengan postingan ini? saya sedang belajar rust lang. Metode yang menurut saya efektif untuk belajar adalah menuliskannya di blog dan menjelaskannya kembali. Saya amat sangat menerima kritik dan saran."

Intro

Post terakhir saya mengenai Rust kurang lebih 4 bulan yang lalu mengenai null value, sudah lama sekali saya tidak belajar mengenai bahasa kepiting ini. Hal yang saya rasakan pertama kali adalah “blank” seperti orang yang lupa segalanya (lebay coy). Akhirnya saya memutuskan kembali belajar rust. Oleh sebab itu, sekarang anda sedang membaca tulisan saya.

match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches.

The book

Match adalah fitur pada bahasa pemrograman rust yang amat sangat berguna, bisa saja sepanjang karir menggunakan bahasa ini, kita tak lepas dari match. Keistimewaanya terletak pada kemampuan match dalam mengidentifikasi berbagai macam pola untuk melakukan eksekusi kode berdasarkan pola yang cocok.

Bayangkan sebuah mesin pemilah koin untuk memisahkan berdasarkan jenis, ukuran, dan kriteria fisik lainnya. Tentunya koin yang akan terpilih sesuai dengan kriterianya masing-masing. Tidak akan ada cerita dimana koin x tertukar dengan koin y.

Catatan: Apabila suatu kata memiliki background abu-abu dan bercetak tebal, maka kata tersebut adalah sebuah syntax/kode.

Main Content

Study Case

Misalnya terdapat suatu koin dari beberapa kerajaan di Indonesia, yaitu koin Kerajaaan Majapahit, Sriwijaya, Ternate, Cirebon, dan Tidore. Kita akan menuliskan sebuah program yang dapat memberikan nilai koin tersebut dalam rupiah.

enum Koin{
    Majapahit,
    Sriwijaya,
    Ternate,
    Tidore,
    Cirebon,
}

fn nilai_dalam_rupiah(koin: Koin) -> u32 {
    match koin {
        Koin::Majapahit => 1000,
        Koin::Sriwijaya => 2000,
        Koin::Ternate => 3000,
        Koin::Tidore => 4000,
        Koin::Cirebon => 5000,
        
    }
}

fn main() {
    let koin1 = Koin::Majapahit;
    
    println!("Nilai koin Majapahit: {} rupiah",   
    nilai_dalam_rupiah(koin1));
}

Contoh 1.0 Penerapan match control flow

Struktur penggunaan match HAMPIR sama dengan if, tetapi terdapat perbedaan karena if akan mengevaluasi suatu kondisi berdasarkan boolean value (true atau false). Sedangkan match dapat mengevaluasi suatu kondisi dengan tipe yang lebih luas, dalam contoh ini adalah enum Koin.

match koin {
        // Arm 1
        Koin::Majapahit => 1000
        // Arm 2
        Koin::Sriwijaya => 2000,        
        // Arm 3
        Koin::Ternate => 3000,        
        // Arm 4
        Koin::Tidore => 4000,        
        // Arm 5
        Koin::Cirebon => 5000,
        }

Contoh 1.1 arms

match memiliki sebuah fitur yang bernama arms dengan struktur:

pola +operator(=>) + kode

Pola yang kita gunakan adalah Koin::Majapahit dengan kode berupa angka (u32), yaitu 1000. Setiap arm dipisahkan dengan menggunakan tanda “,“.

Nilai koin Majapahit: 1000 rupiah
Nilai koin Tidore: 4000 rupiah

Contoh 1.2 output dari kode yang ditulis

Saat match berjalan maka perbandingan dilakukan dari arm 1 – arm 5, jika terdapat arm yang cocok maka kode dieksekusi. Apabila arm 1 tidak cocok akan diteruskan secara berurutan sampai arm 5. Pada contoh 1.1 kita hanya menggunakan kode sederhana berupa angka pada setiapa arm, bilamana kita ingin memasukan lebih dari 1 kode maka diharuskan menggunakan {}.

Pattern yang Ditambah Value

Terdapat dua jenis koin Kerajaan Majapahit, yaitu koin yang berasal dari wilayah Utara dan Selatan. Bagaimana jika kita ingin mengetahui asal daerah koin Majapahit agar tidak tertukar? jawabannya adalah menambahkan value pada pattern match.

// Membuat enum baru untuk wilayah asal dari koin Majapahit
#[derive(Debug)]
enum MajapahitLoc {
    Selatan,
    Utara,
}

enum Koin{
    Majapahit(MajapahitLoc),
    Sriwijaya,
    Ternate,
    Tidore,
    Cirebon,
}


fn nilai_dalam_rupiah(koin: Koin) -> u32 {
    match koin {
        Koin::Sriwijaya => 2000,
        Koin::Ternate => 3000,
        Koin::Tidore => 4000,
        Koin::Cirebon => 5000,
        // Menambahkan variabel "wilayah" pada pattern arm
        Koin::Majapahit(wilayah) => {
            println!("Koin ini berasal dari Majapahit {:?}!", wilayah);
            1000
        }
        
    }
}


fn main() {
    nilai_dalam_rupiah(Koin::Majapahit(MajapahitLoc::Utara));
}

Contoh 1.3 solusi

Pertama kita membuat enum baru dengan nama MajapahitLoc yang di dalamnya terdapa wilayah Selatan dan Utara. Kemudian dibagian arm pattern 5 yang awalnya hanya Coin::Majapahit ditambahkan variabel wilayah sehingga menjadi Coin::Majapahit(wilayah) =>.

Pada saat arm 5 cocok maka variabel tersebut akan terhubung dengan asal wilayah koin Majapahit. Kita juga bisa menggunakan variabel wilayah yang telah memiliki nilai tersebut pada kode.

Coin::Majapahit(wilayah) => {
            println!("Koin ini berasal dari Majapahit {:?}!", wilayah);
            1000
        }

Contoh 1.4 menggunakan variabel dari pattern

Perhatikan output dari kode yang telah kita tulis pada contoh 1.3, hasilnya adalah kita mengetahui asal wilayah koin tersebut. Tadaaa, selamat telah membuat contoh sederhana dari sorting-machine.

Koin ini berasal dari Majapahit Utara!

Contoh 1.5 output

Menggunakan Option<T> Arm Pattern

fn main() {
    fn tambah_satu (x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i+1),
        }
    }

    let five = Some(5);
    let six = tambah_satu(five);
    let none = tambah_satu(None);
    
    println!("Six {:?} dan none = {:?}", six, none);

}

Contoh 1.6 matching with option <T>

Pada contoh 1.6 kita menggunakan inner function (berada dalama fungsi utama fn main), yaitu fn tambah_satu yang akan menerima value Option<i32> menggunakan variabel x. Nilai tersebut berasal dari variabel let five.

Pemindahan value pada fungsi fn tambah_satu menggunakan dua variabel baru, yaitu let six dan let none. Value dari dua variabel tersebut adalah tambah_satu(five) dan tambah_satu(None).

Six Some(6) dan none = None

Contoh 1.7 Output code

Match si Perfeksionis

fn main() {
    fn tambah_satu (x: Option<i32>) -> Option<i32> {
        match x {
            Some(i) => Some(i+1),
        }
    }

    let five = Some(5);
    let six = tambah_satu(five);
    let none = tambah_satu(None);
}

Contoh 1.8 macth yang benar-benar perfeksionis

Judul bagian ini hanya candaan, arm pattern match haruslah mencakup semua kemungkinan yang terjadi (lengkap). Oleh karena itu, jika kita mencoba menjalankan kode pada contoh 1.8 akan terjadi error, mengapa bisa terjadi? rust tidak ingin membuat kesalahan fatal untuk program kita nantinya.

 Compiling hello-word v0.1.0 (/home/hygge/Documents/Rust/hello-word)
warning: unused variable: `six`
 --> src/main.rs:9:9
  |
9 |     let six = tambah_satu(five);
  |         ^^^ help: if this is intentional, prefix it with an underscore: `_six`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `none`
  --> src/main.rs:10:9
   |
10 |     let none = tambah_satu(None);
   |         ^^^^ help: if this is intentional, prefix it with an underscore: `_none`

error[E0004]: non-exhaustive patterns: `None` not covered
   --> src/main.rs:3:15
    |
3   |         match x {
    |               ^ pattern `None` not covered

Contoh 1.9 kode tidak bisa kita compile karena terdapat error

Perhatikan bahwa pada kode 1.8 tidak terdapat arm None => None, sehingga ketika kita menjalankan kode tersebut akan terjadi error. Hal ini terjadi karena match tidak memiliki arm untuk meng-handle sesuatu yang tidak memiliki value (None). Langkah ini juga mencegah masalah null valuethe billion-dollar mistake“.

Pattern Untuk Segala Kemungkinan

Segala kemungkinan? betul sekali. Bayangkan anda adalah ketua suatu kegiatan yang akan menyiapkan doorprize dengan sistem acak menggunakan kertas berisi angka hadiah.

  • Angka 3 berarti peserta mendapatkan tambahan hadiah.
  • Angka 7 mendapatkan tambahan diskon.
  • Selain dari angka 3 dan 7 mendapatkan tambahan cashback .
fn main(){
    let kocok_dadu = 9;
    match kocok_dadu {
        3 => tambah_hadiah(),
        7 => tambah_diskon(),
        other => tambah_cashback(),
    }

    fn tambah_hadiah(){}
    fn tambah_diskon(){}
    fn tambah_cashback(num_spaces: u8){}
}

Contoh 2.0 penggunaan cath alll pattern

Segala kemungkinan akan di-handle oleh arm other => tambah_cashback() berupa angka selain dari 3 dan 7. Ingat bahwa match harus dapat melakukan handling terhadap segala kemungkinan.

Bagaimana jika kita ingin pemain yang mendapatkan angka 3 dan 7 mendapatkan tulisan coba lagi?

Rust memungkinkan keinginan anda terpenuhi menggunakan garis bawah _ sehingga kodenya menjadi:

fn main(){
    let kocok_dadu = 9;
    match kocok_dadu {
        3 => tambah_hadiah(),
        7 => tambah_diskon(),
        _ => tulisan_coba_lagi(),
    }

    fn tambah_hadiah(){}
    fn tambah_diskon(){}
    fn tulisan_coba_lagi(){}
}

Contoh 2.0 penggunaan _

Peserta yang mendapatkan angka selain dari 3 akan mendapatkan tulisan coba lagi. Penggunaan garis bawah membuat arm tidak melakukan bind pada value apapun.

Bagaimana jika pemain yang mendapat angka selain 3 dan 7, benar-benar tidak mendapatkan apapun termasuk tulisan coba lagi?

fn main(){
    let kocok_dadu = 9;
    match kocok_dadu {
        3 => tambah_hadiah(),
        7 => tambah_diskon(),
        other => (),
    }

    fn tambah_hadiah(){}
    fn tambah_diskon(){}
}

Contoh 2.0 arm yang tidak menggunakan value sama sekali

Pada contoh 2.0 menandakan bahwa kita tidak akan menggunakan semua value selain dari arm sebelumnya. Tidak ada pendekatan apapun yang dilakukan oleh arm other => (),.

Penutup

Saya ucapkan terima kasih telah membaca tulisan ini sampai bagian penutup. Komentar dan saran sangat saya hargai. Ingat pepatah china yang berkata:

不经冬寒,不知春暖


Discover more from Qorinotes

Subscribe to get the latest posts sent to your email.

Qori Avatar

Published by

Categories: