Enum dengan data sudah kita kuasai: setiap variant boleh membawa muatan bertipe berbeda, dan match membongkarnya dengan pola. Kini kita tumpangi kemampuan itu dengan type parameter, dan enum menjadi wadah yang muatannya berubah-ubah sesuai pemakai. Lebih menarik lagi, babak ini mengandung pengungkapan: Option dan Result yang kita pakai sejak Section Struktur Data ternyata adalah enum generik buatan standard library. Setelah menyadari hal itu, sintaks Option<String> yang selama ini kita tulis sebagai anotasi tipe akan terbaca sebagai instansiasi — persis seperti Locker<String> pada chapter sebelumnya.
Enum Milik Sendiri dengan Muatan Fleksibel #
Kita bangun pelacak status pengiriman paket. Sebuah paket bisa sedang mengantre, dibawa kendaraan tertentu, atau sudah tiba di alamat tujuan — dan alamat itu bisa berupa String, bisa pula tipe identitas lain tergantung siapa yang memakai enum ini:
enum Delivery<T> {
Queued,
OnVehicle(u8),
Delivered(T),
}
fn main() {
let statuses = [
Delivery::Queued,
Delivery::OnVehicle(3),
Delivery::Delivered(String::from("Jalan Kenanga No. 12")),
];
for status in &statuses {
let label = match status {
Delivery::Queued => String::from("masih menunggu kurir"),
Delivery::OnVehicle(vehicle) => format!("dikendaraan oleh kurir nomor {}", vehicle),
Delivery::Delivered(address) => format!("sampai ke {}", address),
};
println!("Status: {}", label);
}
}Output yang Dihasilkan:
Status: masih menunggu kurir
Status: dikendaraan oleh kurir nomor 3
Status: sampai ke Jalan Kenanga No. 12Mari kita bedah kode di atas baris per baris:
- Baris 1 (
enum Delivery<T> {): type parameterTdideklarasikan setelah nama enum dan berlaku untuk seluruh body. Enum generik mengikuti aturan yang sama dengan struct generik dari chapter sebelumnya. - Baris 2 (
Queued,): variant tanpa data — variant ini sama sekali tidak memakaiT, dan itu diperbolehkan. Tidak semua variant wajib menyentuh type parameter. - Baris 3 (
OnVehicle(u8),): variant berdata dengan tipe tetap. Nomor kendaraan memang selaluu8untuk semua pemakai enum ini. - Baris 4 (
Delivered(T),): variant berdata dengan muatan generik. Pemakai yang butuh alamatStringmendapatDelivery<String>; pemakai lain bisa memakaiDelivery<u32>untuk kode area. - Baris 8-12 (array
statuses): tiga nilai disimpan dalam satu array. VariantQueueddanOnVehicle(3)tidak memberi petunjuk tentangT, tetapi elemenDelivered(String::from(...))menegaskanT = String, dan inferensi menyatukan seluruh array menjadi[Delivery<String>; 3]. - Baris 15-19 (
match status): pencocokan pola berjalan persis seperti enum dengan data biasa — variant dicocokkan namanya, muatan diikat ke variabelvehicledanaddress. Pada instansiasi iniaddressbertipeString(via reference&Stringkarena iterasi meminjam array).
Option dan Result Telah Lama Menjadi Enum Generik #
Sekarang momen pengungkapannya. Definisi Option dan Result di standard library — versi yang disederhanakan — tampak seperti ini:
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}Dua enum ini persis pola Delivery<T> yang baru kita tulis. Option mendeklarasikan satu type parameter: Some(T) membawa nilai keberhasilan-ada, None tidak membawa apa-apa. Result mendeklarasikan dua: Ok(T) membawa nilai sukses, Err(E) membawa data error — perhatikan penamaan T dan E yang deskriptif untuk success type dan error type. Prelude Rust — daftar nama yang diimpor otomatis ke setiap file — memuat keduanya, sehingga Option<String> dan Result<u8, String> langsung tersedia tanpa use apa pun. (Kedua definisi di atas adalah ilustrasi bentuk aslinya; kita tidak perlu dan tidak boleh mendefinisikannya ulang karena prelude sudah menyediakannya.)
Seluruh fitur yang selama ini kita nikmati dari kedua enum itu kini punya penjelasan struktural. Anotasi Option<u32> adalah pemilihan instansiasi, sama seperti Locker<String>. Pencocokan match Some(value) bekerja karena variant-nya memang Some(T). Dan aturan bahwa Result<T, E> membawa dua tipe yang berbeda peran — nilai sukses dan data error — terbaca langsung dari dua type parameternya.
Wujud pemakaiannya dalam satu program:
fn main() {
let stock: Option<u32> = Some(12);
let memo: Option<String> = Some(String::from("Sisa 3 dus di gudang B"));
let promo: Result<u8, String> = Err(String::from("kode promo kadaluarsa"));
println!("Stok: {:?}", stock);
println!("Catatan: {:?}", memo);
println!("Promo: {:?}", promo);
}Output yang Dihasilkan:
Stok: Some(12)
Catatan: Some("Sisa 3 dus di gudang B")
Promo: Err("kode promo kadaluarsa")Mari kita bedah kode di atas baris per baris:
- Baris 2 (
let stock: Option<u32> = Some(12);): anotasiOption<u32>memilih instansiasi;Some(12)membawa muatanu32. Standard library mengimplementasikanDebuguntukOption<T>selamaTjugaDebug, sehingga{:?}sah mencetaknya. - Baris 3 (
let memo: Option<String> = ...): instansiasi kedua dengan muatanString—Option<String>danOption<u32>adalah dua tipe berbeda meski berasal dari satu definisi enum. - Baris 4 (
let promo: Result<u8, String> = Err(...)): anotasi padaResultini bukan hiasan —Err(String::from(...))hanya menegaskanE = Stringdan sama sekali tidak memberi petunjuk tentangT, sehingga tanpa anotasi penuh kompilator akan kehabisan informasi. NilaiErrmenyimpan data error;Ttetangga suksesnya belum terpakai di nilai ini. - Baris 6-8 (
{:?}): formatter{:?}mencetak enum secara utuh beserta variant dan muatannya, dan keluarannya memperlihatkan struktur internalSome(...)sertaErr(...)secara gamblang.
Kapan Enum Generik Layak Dipakai #
Enum generik bersinar pada dua pola. Pertama, mesin status yang muatannya bergantung pemakai: Delivery<T> tadi, atau widget antarmuka dengan status Loading/Loaded(T) — tipe T dipilih oleh pemanggil, bukan perancang enum. Kedua, tipe wadah serbaguna yang perilaku utamanya mengelilingi muatan: Option dan Result adalah contoh terbesarnya — keduanya tidak peduli isinya apa; keduanya hanya mengelola satu pertanyaan (ada atau tidak, sukses atau gagal). Daftar std akan terus bertambah dengan pola serupa saat kita memasuki tipe pintar pada section berikutnya dari Course ini.
Kedua pola itu berbagi satu ciri: perilaku enum tidak bergantung pada isi muatan. Option memperlakukan Some(12) dan Some("teks") dengan aturan yang sama karena match-nya hanya memisahkan dua variant, bukan mengoperasikan muatannya. Begitu sebuah enum perlu mengoperasikan muatannya — membandingkan, menjumlahkan, mencetak — saat itulah type parameter-nya membutuhkan trait bound, dan babak itu sudah kita kenal dari Chapter Trait Bound.
Ringkasan #
Type parameter <T> pada enum memungkinkan variant membawa muatan yang tipenya diputuskan per pemakai, sementara variant lain boleh memakai tipe tetap atau tanpa data. Dengan lensa ini, Option<T> dan Result<T, E> terbuka isinya: enum generik bawaan std yang instansiasinya — Option<String>, Result<u8, String> — adalah tipe-tipe terpisah yang selama ini kita pakai tanpa menyadarinya. Anotasi tipe eksplisit menjadi penting justru saat data belum memberi petunjuk inferensi, misalnya pada nilai Err yang menyembunyikan T. Enum generik masih menuntut satu penjembatan menuju perilaku: blok impl — dan berbeda dari struct, blok tersebut harus mendeklarasikan ulang parameternya, pokok bahasan Chapter Method Generik.