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. Fungsipapanyang lama punyamatchdengan 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
enum StatusPesanan { ... }menulis daftar variant-nya. Nilai bertipeStatusPesananpasti salah satu dari keempatnya.- Variant ditulis lengkap dengan nama enum-nya:
StatusPesanan::Dimasak. CumaDimasaksaja ditolak:cannot find value `Dimasak` in this scope. matchmemilih cabang sesuai variant-nya. NilaiStatusPesanan::Dimasakmasuk ke cabangStatusPesanan::Dimasak, cabang lain dilewati.#[derive(Debug)]membuat{:?}mencetak nama variant-nya, seperti di struct.
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
Tunai(u32)membawa satu angka tanpa nama, seperti tuple struct.Qris { kode: String }membawa field bernama, seperti struct. Satu enum, data yang beda tipe di tiap variant: inilah yang membuat enum Rust lebih kuat dari enum di bahasa lain, yang cuma daftar nama.- Di
match, polaPembayaran::Tunai(uang)sekaligus mengambil angkanya ke variabeluang, danPembayaran::Qris { kode }mengambil fieldkode. if *uang < totaladalah guard seperti di pelajaran match.uangdi sini pinjaman (&u32), jadi*uangmembaca angkanya. Pembayaran 102000 dengan uang 110000 memberi kembalian 8000; uang 100000 kurang 2000.
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
- Lupa nama enum di depan variant.
Antresaja ditolak dengancannot find value `Antre` in this scope. TulisStatusPesanan::Antre. - Variant baru,
matchlama. E0004non-exhaustive patterns. Tambah cabangnya di tiapmatchyang ditunjuk compiler. _untuk enum sendiri. Lolos compile, tapi variant baru ikut masuk_tanpa ketahuan.- Membandingkan dengan
==tanpaPartialEq.status == StatusPesanan::Antreditolak:binary operation `==` cannot be applied to type `StatusPesanan`. Tambah#[derive(PartialEq)], atau pakaimatch. - Memperlakukan
Option<u32>sepertiu32. Keluarkan isinya denganmatchdulu.
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.