Enum di Rust — Rust

Enum di Rust Enum di Rust jauh lebih powerful daripada enum di kebanyakan bahasa lain — setiap variant bisa menyimpan data! Enum Sederhana #[derive(Debug)]…

Enum adalah tipe yang nilainya pasti salah satu dari daftar pilihan tetap, yang disebut variant. Tiap variant boleh membawa data sendiri. Bayangin papan status pesanan di dapur Kedai Martabak Bang Rusdi, dengan empat kolom: Antre, Dimasak, Siap, Diambil. Tiap tiket ada di tepat satu kolom, gak pernah di dua kolom sekaligus, dan gak ada kolom kelima. Lalu laci kasir: pembeli bayar tunai (dan kasir perlu tahu berapa uangnya) atau lewat QRIS (dan kasir perlu kode transaksinya). Dua cara bayar, dua jenis data yang beda. Papan status itu enum biasa, dan cara bayar itu enum yang variant-nya membawa data.

🧪 Tebakan cepat: Bang Rusdi menambah variant kelima, Dibatalkan, ke enum status. Fungsi papan yang lama punya match dengan cabang untuk keempat status lama, tanpa _. Program lolos compile? Tebak dulu, jawabannya ada di bagian "match harus mencakup semua variant".

Enum dan variant

#[derive(Debug)]
enum StatusPesanan {
    Antre,
    Dimasak,
    Siap,
    Diambil,
}

fn papan(status: &StatusPesanan) -> &str {
    match status {
        StatusPesanan::Antre => "menunggu giliran",
        StatusPesanan::Dimasak => "di atas wajan",
        StatusPesanan::Siap => "siap diambil",
        StatusPesanan::Diambil => "selesai",
    }
}

fn main() {
    let semua = [StatusPesanan::Antre, StatusPesanan::Dimasak, StatusPesanan::Siap, StatusPesanan::Diambil];
    for s in &semua {
        println!("{:?}: {}", s, papan(s));
    }
}
Antre: menunggu giliran
Dimasak: di atas wajan
Siap: siap diambil
Diambil: selesai

Variant yang membawa data

enum Pembayaran {
    Tunai(u32),
    Qris { kode: String },
}

fn proses(bayar: &Pembayaran, total: u32) {
    match bayar {
        Pembayaran::Tunai(uang) if *uang < total => println!("Uang kurang {}", total - uang),
        Pembayaran::Tunai(uang) => println!("Tunai {}, kembalian {}", uang, uang - total),
        Pembayaran::Qris { kode } => println!("QRIS {} lunas", kode),
    }
}

fn main() {
    let total = 102000;
    proses(&Pembayaran::Tunai(110000), total);
    proses(&Pembayaran::Tunai(100000), total);
    proses(&Pembayaran::Qris { kode: String::from("QR-0815") }, total);
}
Tunai 110000, kembalian 8000
Uang kurang 2000
QRIS QR-0815 lunas

match harus mencakup semua variant

Ini kode tebakan tadi: Dibatalkan ditambah ke enum, papan gak diubah.

error[E0004]: non-exhaustive patterns: `&StatusPesanan::Dibatalkan` not covered
  --> src/main.rs:11:11
   |
11 |     match status {
   |           ^^^^^^ pattern `&StatusPesanan::Dibatalkan` not covered
   |
note: `StatusPesanan` defined here
  --> src/main.rs:2:6
   |
 2 | enum StatusPesanan {
   |      ^^^^^^^^^^^^^
...
 7 |     Dibatalkan,
   |     ---------- not covered

Gak lolos. Itu jawaban tebakannya. Sama seperti match angka di pelajaran match, match pada enum wajib mencakup semua kemungkinan, dan untuk enum kemungkinannya adalah semua variant. Bedanya, kalau semua variant sudah ditulis, _ gak perlu. Ini jadi pengingat gratis: begitu variant baru ditambah, compiler menunjuk setiap match yang belum menanganinya. Kalau kamu menulis _ => ..., compiler diam saja, dan pesanan yang dibatalkan diam-diam ikut masuk cabang _. Jadi untuk enum milikmu sendiri, tulis semua variant-nya.

Method di enum

Enum juga punya impl, persis seperti struct. Ini papan status yang maju satu kolom tiap dipanggil:

#[derive(Debug)]
enum StatusPesanan {
    Antre,
    Dimasak,
    Siap,
    Diambil,
}

impl StatusPesanan {
    fn berikutnya(self) -> StatusPesanan {
        match self {
            StatusPesanan::Antre => StatusPesanan::Dimasak,
            StatusPesanan::Dimasak => StatusPesanan::Siap,
            StatusPesanan::Siap => StatusPesanan::Diambil,
            StatusPesanan::Diambil => StatusPesanan::Diambil,
        }
    }
}

fn main() {
    let mut status = StatusPesanan::Antre;
    println!("{:?}", status);
    for _ in 0..3 {
        status = status.berikutnya();
        println!("{:?}", status);
    }
}

Output-nya Antre, Dimasak, Siap, Diambil, satu per baris. berikutnya(self) mengambil status lama dan memberi status baru, jadi status = status.berikutnya(); mengganti isinya. Status yang sudah Diambil tetap Diambil.

Option juga enum

Option yang kamu temui di Vec dan HashMap bukan fitur khusus. Di standard library, ia cuma enum generic dengan dua variant:

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

Karena itu kamu bisa membuatnya sendiri, dan match-nya juga harus lengkap:

fn harga(menu: &str) -> Option<u32> {
    match menu {
        "martabak manis" => Some(32000),
        "martabak telur" => Some(38000),
        "es teh" => Some(5000),
        _ => None,
    }
}

fn main() {
    println!("{:?}", harga("es teh"));
    println!("{:?}", harga("kopi"));
    match harga("martabak telur") {
        Some(h) => println!("Harga: {}", h),
        None => println!("Gak ada di menu"),
    }
}
Some(5000)
None
Harga: 38000

Some dan None bisa ditulis tanpa Option:: karena keduanya sudah dimuat otomatis. Option<u32> bukan u32: harga("es teh") + 2000 ditolak dengan cannot add `{integer}` to `Option<u32>` , jadi isinya harus dikeluarkan dulu dengan match. Cara-cara lain mengolah Option (dan saudaranya, Result) dibahas di pelajaran Result dan Option.

⚠️ Jebakan yang sering ketemu

TL;DR: enum StatusPesanan { Antre, Dimasak, Siap, Diambil } membuat tipe yang nilainya pasti salah satu variant, ditulis StatusPesanan::Dimasak. Variant boleh membawa data dengan tipe berbeda: Tunai(u32) atau Qris { kode: String }, dan match mengambil datanya lewat pola Pembayaran::Tunai(uang). match pada enum wajib mencakup semua variant; tanpa _, compiler menunjuk tiap match yang belum menangani variant baru (E0004). Enum punya impl dan method seperti struct. Option<T> sendiri cuma enum Some(T) atau None, jadi isinya dikeluarkan dengan match, bukan dipakai langsung sebagai angka.

Yang akan kamu pelajari