Pada Chapter match Dasar, kita memakai match untuk memetakan satu nilai ke satu tindakan dengan pola literal, wildcard _, or-pattern |, dan rentang ..=. Kebutuhan pencocokan di program nyata jarang sesederhana itu: satu cabang sering harus menampung banyak nilai sekaligus, memeriksa interval yang panjang, bahkan menggabungkan pencocokan pola dengan syarat tambahan yang melibatkan variabel lain di luar nilai yang dicocokkan. Chapter ini memperdalam tiga perkakas tersebut — multi-pola |, rentang ..=, dan match guard — beserta aturan urutan arm yang menentukan cabang mana yang benar-benar dieksekusi. Pemahaman mekanisme di baliknya penting karena guard berinteraksi langsung dengan pemeriksaan exhaustiveness: kode yang secara logika sudah menutup semua kasus bisa tetap ditolak oleh kompilator.

Beberapa Pola dalam Satu Arm #

Program gudang sering memetakan banyak kode produk berbeda ke satu kategori yang sama. Menulis satu arm untuk setiap kode menghasilkan daftar cabang yang panjang padahal isinya identik. Tanda pipa | menyatukan beberapa pola menjadi satu arm: arm tersebut terpenuhi apabila nilai cocok dengan salah satu pola yang tercantum. Kompilator mencoba alternatif pada | satu per satu dari kiri, dan satu arm boleh memuat gabungan literal serta rentang sekaligus.

rust
fn main() {
    let product_code = 41;

    match product_code {
        1 | 2 | 3 => println!("Kategori: Elektronik."),
        10 | 11 => println!("Kategori: Pakaian."),
        40..=45 => println!("Kategori: Buku."),
        _ => println!("Kode produk tidak dikenal."),
    }
}

Output yang Dihasilkan:

text
Kategori: Buku.

Mari kita bedah kode di atas:

  1. Baris 2 (let product_code = 41;): variabel product_code menyimpan kode produk yang akan diperiksa, dengan nilai 41.
  2. Baris 4 (match product_code {): pencocokan dimulai dari arm paling atas; nilai 41 dibandingkan dengan pola arm pertama.
  3. Baris 5 (1 | 2 | 3 => ...): pola gabungan memuat tiga literal; nilai 41 tidak sama dengan 1, 2, maupun 3, sehingga arm ini dilewati meskipun memuat tiga alternatif.
  4. Baris 6 (10 | 11 => ...): nilai 41 juga tidak cocok dengan 10 atau 11, sehingga pencocokan turun ke arm ketiga.
  5. Baris 7 (40..=45 => ...): rentang inklusif 40..=45 menampung seluruh nilai dari 40 hingga 45; nilai 41 berada di dalamnya sehingga arm ini terpilih dan mencetak kategori Buku.
  6. Baris 8 (_ => ...): arm penutup menampung kode di luar semua pola di atas, misalnya 99; tanpa arm ini, kompilasi gagal karena tipe i32 masih menyisakan kemungkinan nilai.

Perhatikan bahwa | tidak dibatasi jumlah alternatifnya, dan alternatif boleh mencampur bentuk pola — literal berdampingan dengan rentang akan kita lihat di bagian berikutnya.

Rentang Nilai dengan ..= #

Rentang ..= bekerja untuk bilangan bulat maupun char, karena kedua tipe tersebut memiliki urutan yang pasti. Rentang bersifat inklusif di kedua ujung: 90..=100 menampung 90 dan 100 beserta seluruh nilai di antaranya. Pola rentang juga dapat digabung ke dalam |, sehingga satu arm dapat menguji “literal ini, literal itu, atau nilai pada rentang berikut” tanpa memecah menjadi beberapa arm.

rust
fn main() {
    let grade = 'B';
    let code = 15;

    match grade {
        'A'..='C' => println!("Nilai {} masuk kelompok atas.", grade),
        'D'..='E' => println!("Nilai {} perlu perbaikan.", grade),
        _ => println!("Huruf {} bukan nilai yang sah.", grade),
    }

    match code {
        1 | 5 | 10..=20 => println!("Kode {} masuk daftar istimewa.", code),
        _ => println!("Kode {} biasa.", code),
    }
}

Output yang Dihasilkan:

text
Nilai B masuk kelompok atas.
Kode 15 masuk daftar istimewa.

Mari kita uraikan bagian-bagian pentingnya:

  1. Baris 6 ('A'..='C' => ...): rentang char dibandingkan menurut urutan karakter, jadi pola ini cocok untuk 'A', 'B', dan 'C'; nilai 'B' masuk sehingga kelompok atas tercetak.
  2. Baris 7 ('D'..='E' => ...): arm ini hanya tercapai untuk 'D' dan 'E'; karena 'B' sudah tertangkap arm di atasnya, arm ini tidak diperiksa lagi.
  3. Baris 8 (_ => ...): huruf di luar 'A'..='E' — misalnya 'X' — jatuh ke wildcard, sehingga match tetap exhaustive.
  4. Baris 12 (1 | 5 | 10..=20 => ...): satu arm memuat dua literal dan satu rentang; nilai 15 tidak sama dengan 1 atau 5, tetapi berada di dalam 10..=20, sehingga arm terpenuhi.

Match Guard #

Pencocokan nilai ujian ke predikat pada Chapter sebelumnya hanya memeriksa satu angka. Kriteria kelulusan nyata sering melibatkan data kedua: nilai minimal 80 tetap gagal predikat apabila kehadiran di bawah 75 persen. Kondisi semacam ini tidak dapat dinyatakan dengan pola murni, karena pola hanya melihat nilai yang dicocokkan. Rust menyediakan match guard: kata kunci if yang ditempatkan setelah pola, sehingga arm hanya terpilih apabila polanya cocok dan guard bernilai true.

rust
fn main() {
    let score = 85;
    let attendance = 60;

    let status = match score {
        s if s >= 80 && attendance >= 75 => "lulus dengan predikat",
        s if s >= 80 => "lulus, tingkatkan kehadiran",
        s if s >= 60 => "lulus bersyarat",
        _ => "tidak lulus",
    };

    println!("Status: {}.", status);
}

Output yang Dihasilkan:

text
Status: lulus, tingkatkan kehadiran.

Mari kita bedah baris per baris:

  1. Baris 5 (let status = match score {): seluruh match menjadi ekspresi yang hasilnya diikat ke variabel status; setiap arm kini berupa string &str.
  2. Baris 6 (s if s >= 80 && attendance >= 75 => ...): pola s mengikat nilai 85, lalu guard dievaluasi; attendance bernilai 60 sehingga attendance >= 75 bernilai false, dan guard menolak arm ini walau polanya cocok.
  3. Baris 7 (s if s >= 80 => ...): pencocokan tidak berhenti saat guard gagal — Rust turun ke arm berikutnya dan mencoba lagi; s mengikat ulang nilai 85, guard s >= 80 bernilai true, sehingga arm ini yang menyumbang hasil.
  4. Baris 8–9 (s if s >= 60 => ... dan _ => ...): kedua arm ini menjadi jaring pengaman untuk nilai di bawah 80; wildcard _ tetap wajib agar pencakupan nilai i32 genap.
  5. Baris 12 (println!("Status: {}.", status);): status akhir dicetak sekali setelah keputusan selesai.

Perhatikan bahwa guard pada baris 6 membaca variabel attendance yang berada di luar match — guard boleh memakai ekspresi apa pun, termasuk data dari luar nilai yang dicocokkan. Alur lengkap evaluasi satu arm dapat diringkas sebagai berikut:

Diagram di atas menegaskan urutan dua tahap pada setiap arm: kecocokan pola diperiksa lebih dulu, guard hanya dievaluasi setelah pola cocok. Ketika guard menolak, proses pencocokan berlanjut ke arm di bawahnya seolah arm tersebut tidak pernah ada.

Guard Tidak Dihitung dalam Exhaustiveness #

Pemeriksaan exhaustiveness hanya memperhitungkan pola, bukan isi guard. Kompilator menganggap guard selalu mungkin bernilai false, sekalipun syaratnya tampak selalu benar seperti 3 > 2. Akibatnya, match yang seluruh cakupannya bergantung pada guard akan ditolak. Program berikut memanggil varian true dari bool hanya apabila jumlah pembelian memenuhi syarat:

rust
fn main() {
    let is_member = true;
    let purchases = 5;

    match is_member {
        true if purchases >= 3 => println!("Anggota aktif, dapat hadiah."),
        false => println!("Bukan anggota."),
    }
}

Pesan error kompilator:

text
error[E0004]: non-exhaustive patterns: `true` not covered
 --> main.rs:5:11
  |
5 |     match is_member {
  |           ^^^^^^^^^ pattern `true` not covered
  |
  = note: the matched value is of type `bool`
  = note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
  |
7 ~         false => println!("Bukan anggota."),
8 ~         true => todo!(),
  |

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0004`.

Baris catatan match arms with guards don't count towards exhaustivity menyatakan aturannya secara terbuka: pola true if purchases >= 3 dianggap tidak menutup nilai true karena guard dapat menolaknya, sedangkan arm false hanya menutup false. Perbaikannya ada dua: menambahkan arm true tanpa guard sebagai penampung, atau menutup sisa dengan _.

Urutan Arm dan Pola yang Tak Terjangkau #

Karena pencocokan berjalan dari arm paling atas, penempatan _ atau rentang luas di urutan pertama membuat arm di bawahnya tidak pernah terjangkau. Kompilator tidak menjadikan kondisi ini error, melainkan memberikan warning unreachable pattern — program tetap berjalan, tetapi cabang di bawah wildcard menjadi kode mati.

rust
fn main() {
    let rating = 5;

    match rating {
        _ => println!("Semua rating dicatat."),
        1..=5 => println!("Rating rendah."),
        6..=10 => println!("Rating tinggi."),
    }
}

Pesan kompilator (warning):

text
warning: unreachable pattern
 --> main.rs:6:9
  |
5 |         _ => println!("Semua rating dicatat."),
  |         - matches any value
6 |         1..=5 => println!("Rating rendah."),
  |         ^^^^^ no value can reach this
  |
  = note: `#[warn(unreachable_patterns)]` (part of `#[warn(unused)]`) on by default

warning: unreachable pattern
 --> main.rs:7:9
  |
5 |         _ => println!("Semua rating dicatat."),
  |         - matches any value
6 |         1..=5 => println!("Rating rendah."),
7 |         6..=10 => println!("Rating tinggi."),
  |         ^^^^^^ no value can reach this

warning: 2 warnings emitted

Dua warning tersebut menandai rentang 1..=5 dan 6..=10 yang sama-sama tak terjangkau. Program kemudian tetap berjalan dan menghasilkan keluaran berikut:

Output yang Dihasilkan:

text
Semua rating dicatat.

Arm 1..=5 dan 6..=10 tidak pernah mendapat kesempatan diperiksa karena _ pada baris 5 menangkap semua nilai. Perbaikannya sederhana: pindahkan wildcard ke posisi paling bawah setelah semua pola spesifik.

Ringkasan #

Tanda | menyatukan banyak pola dalam satu arm, rentang ..= menampung interval nilai untuk bilangan bulat dan char, dan match guard menambahkan syarat if yang hanya dievaluasi setelah pola cocok. Guard membaca variabel luar match dan menolak arm tanpa menghentikan proses pencocokan, tetapi guard tidak pernah dihitung dalam pemeriksaan exhaustiveness sehingga wildcard atau arm tanpa guard tetap dibutuhkan. Urutan arm dari spesifik ke umum mencegah pola tak terjangkau yang memicu warning kompilator. Semua pola pada chapter ini masih berupa nilai tunggal; chapter berikutnya menguraikan struct field demi field langsung di dalam pola.