match menuntut setiap kemungkinan nilai tertangani — jaminan yang sangat berguna untuk enum dengan varian banyak, tetapi berlebihan ketika kita hanya peduli satu pola. Memeriksa apakah sebuah Option berisi Some tidak seharusnya memaksa kita menulis arm None yang kosong. Rust menyediakan if let untuk kebutuhan tersebut: konstruksi yang menguji tepat satu pola, menjalankan blok ketika polanya cocok, dan menawarkan else untuk sisanya. Chapter ini membahas sintaksnya, penerapan pada enum buatan sendiri, serta konsep di baliknya — pattern refutable dan irrefutable — yang menentukan pola mana yang sah untuk let, if let, dan match.
Masalah: match Penuh untuk Satu Pola #
Mari kita mulai dari bentuk yang terasa mubazir. Program berikut hanya ingin mencetak bahasa favorit ketika datanya ada:
fn main() {
let favorite: Option<&str> = Some("Rust");
match favorite {
Some(language) => println!("Bahasa favorit: {}.", language),
None => println!("Belum memilih bahasa favorit."),
}
}Output yang Dihasilkan:
Bahasa favorit: Rust.Pada baris 4–7 (match favorite { ... }), arm Some(language) adalah satu-satunya yang berisi logika yang benar-benar kita rancang; arm None hadir semata memenuhi tuntutan exhaustiveness. Arm seperti ini akan berulang di seluruh kode yang memakai Option — kebanyakan dengan isi todo!(), {}, atau komentar “tidak melakukan apa-apa”. Kebutuhan “satu pola penting, sisanya opsional” ini yang hendak dijawab oleh if let.
Sintaks if let #
if let digunakan dengan bentuk if let POLA = NILAI. Mekanismenya: nilai di sisi kanan tanda sama dengan dievaluasi sekali, pola di sisi kiri dicobakan terhadap nilai tersebut, dan blok if berjalan hanya apabila pola cocok — menggantikan perbandingan true/false pada if biasa. Blok else berjalan ketika pola gagal:
fn main() {
let favorite: Option<&str> = Some("Rust");
if let Some(language) = favorite {
println!("Bahasa favorit: {}.", language);
} else {
println!("Belum memilih bahasa favorit.");
}
}Output yang Dihasilkan:
Bahasa favorit: Rust.Mari kita bedah baris per baris:
- Baris 4 (
if let Some(language) = favorite {): polaSome(language)diuji terhadapfavorite; varianSomecocok sehingga isi"Rust"terikat ke variabellanguage— hasil pengikatan ini menggantikan nilai boolean pada kondisiifbiasa. - Baris 5 (
println!("Bahasa favorit: {}.", language);): variabellanguagehanya hidup di dalam blok ini; setelah blok selesai,languagekeluar dari scope. - Baris 6–7 (
} else {): cabangelseberjalan ketika pola gagal — yaitu ketikafavoritebernilaiNone; pada data kita cabang ini tidak dieksekusi.
Tidak ada tuntutan exhaustiveness di sini karena if let memang tidak mencoba meng-cover seluruh ruang nilai — pola gagal cukup dialirkan ke else. Ketika else tidak ditulis, kegagalan pola berarti seluruh if let dilewati dan eksekusi berlanjut ke statement berikutnya.
if let pada Enum Buatan Sendiri #
if let tidak terbatas pada Option. Enum apa pun dengan varian berdata dapat diperiksa polanya satu per satu. Contoh berikut memproses daftar sesi login dan hanya menyambut sesi yang benar-benar masuk:
enum LoginState {
LoggedIn(String),
LoggedOut,
}
fn main() {
let sessions = [
LoginState::LoggedIn(String::from("Budi")),
LoginState::LoggedOut,
];
for session in sessions {
if let LoginState::LoggedIn(username) = session {
println!("Selamat datang, {}!", username);
}
}
}Output yang Dihasilkan:
Selamat datang, Budi!Mari kita bedah kode di atas:
- Baris 7–10 (
let sessions = [...];): array memuat dua keadaan — satu sesi masuk membawa namaString, satu sesi keluar tanpa data; enum ini tidakCopy, sehingga setiap elemen dipindahkan kesessionsaat iterasi. - Baris 12 (
for session in sessions {): perulangan mengonsumsi elemen array satu per satu; keduanya akan melewati pengujian pola yang sama. - Baris 13 (
if let LoginState::LoggedIn(username) = session {): hanya varianLoggedInyang diuji; elemen kedua bertipeLoggedOutgagal pada pola ini dan dilewati tanpa perlu arm khusus. - Baris 14 (
println!("Selamat datang, {}!", username);): pengikatanusernamemenerima kepemilikanStringdari dalam varian — nama pengguna berpindah darisessionkeusername, lalu dicetak.
Perhatikan akibat pengikatan by value pada baris 13: karena String tidak Copy, isi LoggedIn berpindah dan variabel session tidak dapat dipakai lagi setelah pencocokan. Ketika data varian perlu dibaca tanpa berpindah kepemilikan, pola dapat meminjamkannya dengan ref — pokok bahasan chapter ref dan ref mut di Pola.
Pola Refutable dan Irrefutable #
Konsep kunci di balik perbedaan let dan if let adalah sifat pola terhadap nilai yang dicobakannya. Sebuah pola disebut irrefutable apabila cocok untuk setiap nilai dari tipe yang bersangkutan, dan refutable apabila ada nilai yang tidak cocok. Pengikatan sederhana x menerima angka apa pun, pola (a, b) cocok untuk semua tuple dua komponen, dan pola struct lengkap cocok untuk semua nilai struct tersebut — ketiganya irrefutable. Sebaliknya Some(qty) gagal pada None, literal 1 gagal pada angka lain, dan rentang 1..=5 gagal pada 6 — ketiganya refutable.
| Pola | Sifat | Alasan |
|---|---|---|
x | irrefutable | mengikat nilai apa pun |
(a, b) pada tuple dua komponen | irrefutable | semua komponen terikat |
Point { x, y } dengan field lengkap | irrefutable | semua field terikat |
Some(qty) | refutable | None tidak cocok |
1..=5 | refutable | nilai di luar rentang tidak cocok |
Dari sifat ini lahir dua aturan: let hanya menerima pola irrefutable, sedangkan if let, while let, dan arm match menerima pola refutable sekalipun. Program berikut melanggar aturan pertama:
fn main() {
let stock: Option<u32> = Some(12);
let Some(qty) = stock;
println!("Stok: {} unit.", qty);
}Pesan error kompilator:
error[E0005]: refutable pattern in local binding
--> main.rs:4:9
|
4 | let Some(qty) = stock;
| ^^^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
= note: the matched value is of type `Option<u32>`
help: you might want to use `let...else` to handle the variant that isn't matched
|
4 | let Some(qty) = stock else { todo!() };
| ++++++++++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0005`.Pesan E0005 menegaskan alasan penolakannya: let Some(qty) tidak punya tempat menampung None. Bila stock ternyata kosong, variabel qty tidak akan pernah terisi — keadaan yang tidak boleh terjadi pada let yang wajib selalu berhasil. Saran let...else pada pesan tersebut adalah bentuk lanjutan yang berada di luar cakupan section ini; intinya sama, yaitu menyediakan jalur untuk pola yang gagal.
Kebalikannya terjadi ketika if let menerima pola yang justru selalu berhasil:
fn main() {
if let x = 5 {
println!("Nilai x: {}.", x);
}
}Pesan kompilator (warning):
warning: irrefutable `if let` pattern
--> main.rs:2:8
|
2 | if let x = 5 {
| ^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default
warning: 1 warning emittedProgram ini tetap berjalan dan menghasilkan keluaran berikut:
Output yang Dihasilkan:
Nilai x: 5.Pengikatan x cocok untuk nilai apa pun, sehingga blok if selalu berjalan dan cabang gagal tidak pernah ada. Warning ini bukan galat: rustc hanya memberi tahu bahwa if let di posisi tersebut kehilangan kemampuannya menyaring, dan menyarankan penggantian dengan let biasa.
Ringkasan #
if let menguji satu pola terhadap satu nilai: blok utama berjalan ketika pola cocok, else menampung kegagalannya, dan binding dari pola hanya hidup di dalam blok. Konstruksi ini menggantikan match lengkap ketika hanya satu pola yang penting, baik pada Option maupun enum buatan sendiri. Sifat pola menentukan tempatnya — pola irrefutable wajib untuk let (pelanggarannya memicu E0005), pola refutable hanya sah pada if let dan kerabatnya, dan pola irrefutable di dalam if let memicu warning karena penyaringannya percuma. Chapter berikutnya membawa pola ke perulangan: while let terus memproses data selama polanya terus cocok.