Dalam sepuluh chapter terakhir, kita mengumpulkan dua bentuk dasar perancangan data: struct yang menghimpun field-field menjadi satu record, dan enum yang memilih satu variant dari beberapa kemungkinan. Alat yang lengkap justru menimbulkan pertanyaan baru setiap kali kita mulai mendesain: data ini sebaiknya struct atau enum? Kesalahan pemilihan jarang terasa saat program pertama kali berjalan, tetapi menumpuk menjadi kode yang canggung — field yang setengah kosong, pemeriksaan if yang berulang di setiap fungsi, atau daftar kemungkinan yang bocor keluar dari definisi tipe. Pada chapter penutup Section ini kita akan merumuskan pedoman memilih, lalu mengujinya pada studi kasus pesanan toko daring yang menggabungkan kedua bentuk dalam satu desain.
Record dan Variasi: Dua Bentuk Dasar Data #
Cara berpikir paling cepat adalah membandingkan dua kata hubung. Struct adalah jawaban atas pertanyaan “data ini terdiri dari apa saja?” — jawabannya berupa daftar yang seluruhnya hadir sekaligus: pesanan terdiri dari kode, pembeli, total, dan metode pembayaran. Semua field tersedia setiap saat, tidak ada yang boleh absen, dan hubungannya adalah dan. Enum adalah jawaban atas pertanyaan “data ini adalah yang mana dari beberapa kemungkinan?” — jawabannya tepat satu pilihan pada satu waktu: metode pembayarannya transfer, e-wallet, atau bayar di tempat. Hubungannya adalah atau.
Konsekuensi keduanya menjalar ke seluruh kode. Struct menjamin kelengkapan: field total: u64 selalu ada, jadi fungsi penghitung pajak tidak perlu bertanya “kalau tidak ada, bagaimana?”. Enum menjamin keeksklusifan: pesanan berbayar lewat e-wallet tidak mungkin sekaligus berbayar di tempat, jadi tidak ada kombinasi mustahil yang harus dijaga. Ketika satu dari dua jaminan ini justru menghalangi pemodelan yang jujur, tanda kita memilih bentuk yang salah.
Pedoman Cepat Memilih #
Kerangka pertanyaan berikut biasanya cukup untuk menutup sebagian besar keputusan:
- Semua bagian hadir bersama? Bila ya — pesanan selalu punya kode, pembeli, total — pakai struct.
- Hanya satu pilihan pada satu waktu? Bila ya — status, kategori, jenis peristiwa — pakai enum.
- Field-nya opsional satu per satu? Hati-hati: ini bukan otomatis berarti struct dengan
Optiondi mana-mana. Bila yang opsional sebenarnya “salah satu dari beberapa bentuk”, enum dengan data adalah jawaban yang lebih jujur. - Campuran? Program nyata hampir selalu campuran: struct menampung enum sebagai field, atau variant enum menampung struct di dalam datanya.
Justru poin terakhir yang paling penting: struct dan enum bukan dua kampung yang bersaing. Desain yang baik biasanya menyusun keduanya berlapis, dengan struct sebagai kerangka entitas dan enum sebagai penjelmaahan pilihan di dalamnya.
Studi Kasus: Pesanan dan Metode Pembayarannya #
Mari kita terapkan kerangka tersebut. Entitas utama toko daring adalah Order — kode, pembeli, total selalu hadir bersama, jadi struct. Metode pembayaran adalah pilihan eksklusif dengan bentuk data berbeda-beda: transfer butuh nama bank, e-wallet butuh provider plus nomor telepon, bayar di tempat tidak butuh apa pun — tiga variant enum. Ketiganya dirakit menjadi satu:
enum PaymentMethod {
BankTransfer { bank: String },
EWallet { provider: String, phone: String },
CashOnDelivery,
}
struct Order {
code: String,
buyer: String,
total: u64,
payment: PaymentMethod,
}
fn describe_payment(payment: &PaymentMethod) -> String {
match payment {
PaymentMethod::BankTransfer { bank } => format!("transfer via {}", bank),
PaymentMethod::EWallet { provider, phone } => {
format!("e-wallet {} ({})", provider, phone)
}
PaymentMethod::CashOnDelivery => String::from("bayar di tempat"),
}
}
fn main() {
let orders = [
Order {
code: String::from("ORD-2049"),
buyer: String::from("Siti Rahma"),
total: 175000,
payment: PaymentMethod::EWallet {
provider: String::from("DompetKu"),
phone: String::from("0812-0000-0000"),
},
},
Order {
code: String::from("ORD-2050"),
buyer: String::from("Budi Santoso"),
total: 98000,
payment: PaymentMethod::BankTransfer {
bank: String::from("Bank Sawit Nusantara"),
},
},
Order {
code: String::from("ORD-2051"),
buyer: String::from("Rina Wijaya"),
total: 45000,
payment: PaymentMethod::CashOnDelivery,
},
];
for order in orders {
println!(
"{} ({}) Rp{} - pembayaran: {}",
order.code,
order.buyer,
order.total,
describe_payment(&order.payment)
);
}
}Output yang Dihasilkan:
ORD-2049 (Siti Rahma) Rp175000 - pembayaran: e-wallet DompetKu (0812-0000-0000)
ORD-2050 (Budi Santoso) Rp98000 - pembayaran: transfer via Bank Sawit Nusantara
ORD-2051 (Rina Wijaya) Rp45000 - pembayaran: bayar di tempatMari kita bedah kode di atas baris per baris:
- Baris 1-5 (
enum PaymentMethod { ... }): tiga variant mewakili tiga metode eksklusif.BankTransferdanEWalletadalah struct variant karena membawa data berbentuk berbeda;CashOnDeliverycukup sebagai unit variant. - Baris 7-12 (
struct Order { ... }): kerangka pesanan. Perhatikan fieldpayment: PaymentMethoddi baris 11 — struct menampung enum sebagai field biasa. Pemilihan bentuk menjadi bagian dari tipe, bukan keputusan tiap fungsi. - Baris 14-22 (
fn describe_payment(payment: &PaymentMethod)): satu-satunya tempat yang tahu cara menerjemahkan tiap metode.matchmembongkar data variant — nama bank untuk transfer, provider dan telepon untuk e-wallet — dan setiap pesanan pasti mendapat deskripsi karena ketiga variant tertutup. - Baris 25-49 (
let orders = [ ... ]): tiga pesanan memakai tiga metode berbeda dalam satu array. Setiap instansiasi mengisi fieldpaymentdengan bentuk yang sesuai variant-nya masing-masing. - Baris 51-59 (
for order in orders): loop mencetak seluruh pesanan. Fungsi utama tidak mengurus detail pembayaran — seluruh kerumitan itu tersimpan di dalamdescribe_paymentdan definisi enum.
Kekuatan desain ini menjadi nyata saat toko menambah metode baru — misalnya virtual account. Tambahkan satu variant VirtualAccount { number: String }, dan kompilator langsung menunjuk describe_payment sebagai satu-satunya tempat yang harus diperbarui. Tidak ada fungsi lain yang bisa lupa, karena pemeriksaan exhaustiveness menjangkau seluruh program.
Jebakan Desain: Struct Penuh Option #
Untuk melihat sebaliknya, mari kita sengaja salah pilih. Bayangkan metode pembayaran dimodelkan sebagai struct dengan seluruh kemungkinan sebagai field opsional:
struct PaymentFields {
bank: Option<String>,
provider: Option<String>,
phone: Option<String>,
cash_on_delivery: bool,
buyer: String,
total: u64,
}
fn main() {
let messy = PaymentFields {
bank: Some(String::from("Bank Sawit Nusantara")),
provider: Some(String::from("DompetKu")),
phone: Some(String::from("0812-0000-0000")),
cash_on_delivery: true,
buyer: String::from("Budi Santoso"),
total: 98000,
};
println!("Pesanan {} sebesar Rp{}", messy.buyer, messy.total);
println!("Bank terdaftar: {:?}", messy.bank);
println!("E-wallet terdaftar: {:?}", messy.provider);
println!("Nomor telepon: {:?}", messy.phone);
println!("Bayar di tempat? {}", messy.cash_on_delivery);
}Output yang Dihasilkan:
Pesanan Budi Santoso sebesar Rp98000
Bank terdaftar: Some("Bank Sawit Nusantara")
E-wallet terdaftar: Some("DompetKu")
Nomor telepon: Some("0812-0000-0000")
Bayar di tempat? trueProgram ini sah dan berjalan — itulah yang membuat jebakannya berbahaya. Pesanan Budi di atas terdaftar transfer bank, e-wallet, dan bayar di tempat sekaligus, padahal ketiga kondisi itu saling meniadakan pada dunia nyata. Setiap fungsi yang membaca PaymentFields kini wajib menimbang belasan kombinasi: bank terisi tapi phone kosong? provider terisi tapi cash_on_delivery juga true? Tidak ada jaminan dari tipe bahwa kombinasi mustahil tidak terjadi. Enum PaymentMethod menutup seluruh lubang ini dengan tiga baris definisi — pilihan eksklusif dijamin sistem tipe, bukan diperiksa manual di mana-mana.
Ringkasan #
Struct dan enum menjawab dua pertanyaan yang berbeda: struct menghimpun bagian yang selalu hadir bersama (hubungan dan), enum memilih satu kemungkinan dari daftar tertutup (hubungan atau). Desain matang biasanya menggabungkan keduanya — struct entitas menampung enum pilihan sebagai field, seperti Order yang memegang PaymentMethod — sehingga penambahan kemungkinan baru ditangani kompilator lewat pemeriksaan exhaustiveness. Struct penuh Option yang saling meniadakan adalah penanda pemilihan bentuk yang keliru. Section Struktur Data kita tutup di sini; keterampilan berikutnya yang menunggu adalah mengolah pola secara lebih dalam pada Section Pattern Matching.