Sejak chapter match Dasar, kita berkali-kali menyebut bahwa match wajib mencakup semua kemungkinan nilai — kali ini kita bedah jaminan itu secara menyeluruh. Exhaustiveness checking adalah pemeriksaan pada saat kompilasi yang memverifikasi gabungan pola pada seluruh arm sudah menutup seluruh ruang nilai tipe yang dicocokkan; celah sekecil apa pun menghentikan kompilasi dengan galat E0004. Perlindungan ini terasa paling bernilai justru ketika program berkembang: varian enum baru otomatis membuat setiap match lama yang belum mengenalnya gagal dikompilasi, sehingga tidak ada kasus yang lolos tanpa sengaja. Chapter ini mengupas cara kompilator menghitung cakupan, cara membaca pesan E0004, kecukupan tanpa wildcard, dan taruhan penggunaan _ sebagai penampung serba tangkap.
Bagaimana Kompilator Menghitung Kecukupan #
Proses pemeriksaan berlangsung dalam tiga tahap. Pertama, kompilator menghimpun seluruh kemungkinan nilai dari tipe yang dicocokkan — untuk enum, daftar variannya; untuk bool, dua nilainya; untuk bilangan, rentang penuh tipenya. Kedua, setiap pola pada setiap arm menandai bagian ruang nilai yang dicakupnya; arm dengan guard tidak ikut menandai karena guard dianggap selalu mungkin gagal. Ketiga, kompilator menghitung sisa nilai yang belum tertandai — sisa yang tidak kosong menjadi galat.
Diagram di atas merangkum keputusan kompilator sebagai percabangan tunggal: sisa cakupan kosong berarti lolos, sisa berisi berarti galat lengkap dengan pola yang tidak tertangani. Mari kita lihat galat itu dalam bentuknya yang asli — sebuah enum metode pembayaran yang kurang satu arm:
enum PaymentMethod {
Cash,
BankTransfer,
EWallet,
}
fn main() {
let method = PaymentMethod::EWallet;
match method {
PaymentMethod::Cash => println!("Dibayar tunai."),
PaymentMethod::BankTransfer => println!("Dibayar lewat transfer bank."),
}
}Pesan error kompilator:
error[E0004]: non-exhaustive patterns: `PaymentMethod::EWallet` not covered
--> main.rs:10:11
|
10 | match method {
| ^^^^^^ pattern `PaymentMethod::EWallet` not covered
|
note: `PaymentMethod` defined here
--> main.rs:1:6
|
1 | enum PaymentMethod {
| ^^^^^^^^^^^^^
...
4 | EWallet,
| ------- not covered
= note: the matched value is of type `PaymentMethod`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
12 ~ PaymentMethod::BankTransfer => println!("Dibayar lewat transfer bank."),
13 ~ PaymentMethod::EWallet => todo!(),
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0004`.Pesan E0004 memuat peta lengkap kekurangannya: baris utama menyebut PaymentMethod::EWallet sebagai pola yang tidak tercakup, catatan PaymentMethod defined here menunjuk definisi enum beserta varian yang bolong, dan blok help menawarkan arm PaymentMethod::EWallet => todo!(), yang tinggal diisi logika. Program tidak hanya diberi tahu bahwa kurang; program diberi tahu kurang yang mana dan di mana harus menambahkannya.
Melengkapi Arm Secara Eksplisit #
Perbaikan paling jujur adalah menambahkan arm untuk varian yang disebut galat — tanpa wildcard, sehingga setiap kemungkinan terlihat dan terpelihara:
enum PaymentMethod {
Cash,
BankTransfer,
EWallet,
}
fn main() {
let methods = [
PaymentMethod::Cash,
PaymentMethod::BankTransfer,
PaymentMethod::EWallet,
];
for method in methods {
match method {
PaymentMethod::Cash => println!("Dibayar tunai."),
PaymentMethod::BankTransfer => println!("Dibayar lewat transfer bank."),
PaymentMethod::EWallet => println!("Dibayar lewat dompet digital."),
}
}
}Output yang Dihasilkan:
Dibayar tunai.
Dibayar lewat transfer bank.
Dibayar lewat dompet digital.Mari kita bedah kode di atas:
- Baris 8–12 (
let methods = [...];): ketiga varian dibuat agar setiap arm kebagian dieksekusi — sekaligus menghindari warning varian yang tak pernah dipakai. - Baris 14 (
for method in methods {): setiap elemen dipindahkan kemethodlalu dicocokkan; pencocokan berulang tiga kali, satu kali per varian. - Baris 15–19 (
match method { ... }): tiga arm mengikat tiga varian tanpa wildcard — cakupan genap berasal dari penamaan varian, bukan dari_; varian keempat yang ditambahkan di masa depan akan membatalkan kompilasi sampai arm barunya ditulis.
Kecukupan Tanpa Wildcard #
Exhaustiveness tidak menghitung jumlah arm, melainkan gabungan cakupan pola. Beberapa arm boleh menutupi kombinasi nilai yang saling mengisi, dan pola gabungan seperti or-pattern atau wildcard per posisi boleh merapat untuk menggenapi cakupan. Contoh berikut menangani tuple (bool, bool) — empat kombinasi — hanya dengan tiga arm:
fn main() {
let order = (true, false);
match order {
(true, true) => println!("Sudah dibayar dan sudah dikirim."),
(true, false) => println!("Sudah dibayar, belum dikirim."),
(false, _) => println!("Belum dibayar."),
}
}Output yang Dihasilkan:
Sudah dibayar, belum dikirim.Mari kita telaah pembagiannya:
- Baris 4 (
match order {): nilai bertipe(bool, bool)— ruang nilainya berisi tepat empat kombinasi:(true, true),(true, false),(false, true), dan(false, false). - Baris 5–6 (
(true, true)dan(true, false)): dua kombinasi pertama ditangani eksplisit, satu arm untuk masing-masing. - Baris 7 (
(false, _) => ...): satu arm menampung dua kombinasi sekaligus —(false, true)dan(false, false)— karena posisi kedua diisi_yang menerima nilai apa pun. - Baris 4–8 (keseluruhan): empat kombinasi habis dibagi tiga arm; kompilator menerima
matchini tanpa wildcard terpisah karena hitungannya memang nol sisa.
Perlindungan Saat Kode Berkembang #
Nilai terbesar exhaustiveness bukan pada program yang selesai, melainkan pada program yang berubah. Misalkan sistem pembayaran kita menambahkan varian Qris — cukup tambahkan varian pada enum, lalu biarkan kompilator mendata setiap match yang perlu diperbarui:
enum PaymentMethod {
Cash,
BankTransfer,
EWallet,
Qris,
}
fn main() {
let method = PaymentMethod::Qris;
match method {
PaymentMethod::Cash => println!("Dibayar tunai."),
PaymentMethod::BankTransfer => println!("Dibayar lewat transfer bank."),
PaymentMethod::EWallet => println!("Dibayar lewat dompet digital."),
}
}Pesan error kompilator:
error[E0004]: non-exhaustive patterns: `PaymentMethod::Qris` not covered
--> main.rs:11:11
|
11 | match method {
| ^^^^^^ pattern `PaymentMethod::Qris` not covered
|
note: `PaymentMethod` defined here
--> main.rs:1:6
|
1 | enum PaymentMethod {
| ^^^^^^^^^^^^^
...
5 | Qris,
| ---- not covered
= note: the matched value is of type `PaymentMethod`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
14 ~ PaymentMethod::EWallet => println!("Dibayar lewat dompet digital."),
15 ~ PaymentMethod::Qris => todo!(),
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0004`.match yang kemarin masih valid kini ditolak dengan menyebut PaymentMethod::Qris — nama varian yang bahkan belum pernah ditulis pada file ini sebelumnya. Bandingkan dengan rantai if/else biasa: cabang baru masuk diam-diam, nilai Qris jatuh ke cabang penampung tanpa komplain, dan keputusan bisnis salah bisa berjalan berminggu-minggu sebelum ketahuan. Pada kode besar yang match-nya tersebar di banyak fungsi, satu penambahan varian memicu daftar galat yang sekaligus menjadi daftar tugas pembaruan.
_ sebagai Catch-All dan Taruhannya #
Wildcard _ menutup celah apa pun seketika, dan pola .. melakukan hal serupa untuk sisa field. Kenyamanan ini punya harga: _ juga menutup celah yang belum ada — varian baru di masa depan akan masuk ke arm penampung tanpa peringatan apa pun. Contoh berikut sengaja memakai _ untuk metode pembayaran yang dianggap kurang penting:
enum PaymentMethod {
Cash,
BankTransfer,
EWallet,
}
fn main() {
let methods = [
PaymentMethod::Cash,
PaymentMethod::EWallet,
PaymentMethod::BankTransfer,
];
for method in methods {
let fee = match method {
PaymentMethod::Cash => 0,
PaymentMethod::EWallet => 1_000,
_ => 2_500,
};
println!("Biaya admin: {} rupiah.", fee);
}
}Output yang Dihasilkan:
Biaya admin: 0 rupiah.
Biaya admin: 1000 rupiah.
Biaya admin: 2500 rupiah.Mari kita bedah perannya:
- Baris 15 (
let fee = match method {):matchsebagai ekspresi yang hasilnya diikat kefee— pola kerja yang telah kita pakai sejak chapter match Dasar. - Baris 16–17 (
PaymentMethod::Cash => 0danPaymentMethod::EWallet => 1_000): dua varian dengan tarif khusus ditangani eksplisit karena keduanya merupakan keputusan domain yang penting. - Baris 18 (
_ => 2_500,): wildcard menampung sisanya — pada contoh iniBankTransfertercetak2500; kelak, varianQrisbaru juga akan masuk arm ini dan menerima tarif2_500tanpa satu pun peringatan.
Pedoman praktisnya: nyatakan arm eksplisit untuk setiap varian yang memengaruhi keputusan penting — tarif, status hukum, keamanan — dan sisakan _ hanya untuk penampungan yang memang benar-benar seragam, seperti pencatatan log tanpa perlakuan khusus. Taruhan _ adalah menukar jaminan masa depan dengan keringkasan hari ini.
Ringkasan #
Exhaustiveness checking menghimpun seluruh ruang nilai tipe yang dicocokkan, menandai cakupan setiap pola arm, dan menolak match yang masih menyisakan nilai tak tertangani dengan E0004 — lengkap dengan nama pola yang bolong dan usulan arm perbaikan. Kecukupan dihitung dari gabungan cakupan, bukan jumlah arm, sehingga kombinasi or-pattern, wildcard per posisi, dan penggabungan kombinasi dapat menggenapi tanpa _ terpisah. Varian enum baru otomatis membatalkan setiap match lama yang belum menanganinya — perlindungan yang membuat pembaruan kode terarah — sedangkan _ menghapus perlindungan itu demi keringkasan. Section Pattern Matching tuntas di sini; keahlian menguraikan struct, enum, dan tuple dengan pola akan dipakai terus-menerus di section Koleksi saat kita mengolah Vec dan HashMap.