Di banyak bahasa pemrograman, if hanyalah perintah (statement) yang mengarahkan alur eksekusi dan tidak menghasilkan nilai apa pun; bahasa-bahasa tersebut lalu memerlukan operator ternary kondisi ? a : b untuk memilih nilai secara ringkas. Rust berjalan di arah yang berbeda: if adalah expression — konstruksi yang menghasilkan nilai — sehingga kita dapat menulis let status = if kondisi { nilai_a } else { nilai_b }; tanpa perlu operator ternary sama sekali (Rust memang tidak memilikinya). Pada Chapter ini kita akan memanfaatkan sifat tersebut untuk inisialisasi variabel, memahami dua aturan mainnya, lalu melihat bahwa blok kurung kurawal biasa pun memiliki perilaku yang sama.
if yang Menghasilkan Nilai #
Ketika if dipakai sebagai ekspresi, nilai hasilnya berasal dari blok cabang yang terpenuhi: blok yang dieksekusi menyumbang nilainya, dan cabang lain tidak berperan. Konsekuensinya, kedua cabang — blok if dan blok else — wajib ada; sebuah if tanpa else tidak punya nilai saat kondisi bernilai false, sehingga compiler menolak pemakaiannya sebagai ekspresi. Mari kita mulai dengan contoh paling ringkas, penentuan ganjil atau genap:
fn main() {
let number = 7;
let parity = if number % 2 == 0 { "genap" } else { "ganjil" };
println!("Angka {} adalah bilangan {}.", number, parity);
}Output yang Dihasilkan:
Angka 7 adalah bilangan ganjil.Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 2 (
let number = 7;): variabelnumberdiisi nilai7yang akan diperiksa kelasnya. - Baris 4 (
let parity = if number % 2 == 0 { "genap" } else { "ganjil" };): Rust mengevaluasi kondisi7 % 2 == 0; hasilnyafalse, sehingga cabangelseyang dieksekusi dan nilai"ganjil"menjadi hasil keseluruhan ekspresiif. Nilai tersebut diikat ke variabelparitybertipe&str, kemudian statement ditutup dengan titik koma. - Baris 6 (
println!("Angka {} adalah bilangan {}.", number, parity);): macroprintln!mencetak nilaiparityyang sudah pasti terisi oleh salah satu cabang — tidak ada kemungkinanparityberada tanpa nilai.
Bandingkan dengan padanan bentuk statement yang mengisi variabel dari dalam tiap cabang: dua statement penugasan, variabel yang harus mut, dan pembaca kode yang harus memeriksa kedua cabang untuk tahu nilai mana saja yang mungkin. Bentuk ekspresi menegaskan sejak awal bahwa parity diisi tepat sekali, dari dua kandidat nilai yang terlihat pada satu baris.
Bentuk Multibaris dan else if #
Ekspresi if panjang tidak perlu dipadatkan satu baris. Bentuk multibaris menuliskan tiap cabang pada baris terpisah, tetap tanpa titik koma setelah nilai di tiap cabang, dan menutup statement dengan satu titik koma setelah kurung kurawal penutup else. Rantai else if di tengah rantai tetap sah selama cabang terakhir adalah else:
fn main() {
let stock = 25;
let status = if stock > 20 {
"aman"
} else if stock > 5 {
"menipis"
} else {
"kritis"
};
println!("Status stok: {}", status);
}Output yang Dihasilkan:
Status stok: amanMari kita bedah program di atas:
- Baris 4 (
let status = if stock > 20 {): variabelstatusbersiap menerima hasil ekspresi; kondisi pertama25 > 20bernilaitrue. - Baris 5 (
"aman"): string"aman"ditulis tanpa titik koma karena posisinya adalah ekspresi terakhir blok — nilai inilah yang menjadi hasil cabang; cabangelse ifdanelsetidak pernah dievaluasi. - Baris 10 (
};): kurung kurawal penutupelsediikuti titik koma yang menutup keseluruhan statementlet; titik koma tidak pernah ditulis di dalam cabang. - Baris 12 (
println!("Status stok: {}", status);): nilai"aman"dari variabelstatusdicetak sebagai laporan status gudang.
Kedua Cabang Harus Bertipe Sama #
Aturan main pertama pemakaian if sebagai ekspresi: semua cabang harus menghasilkan tipe yang sama. Compiler harus dapat menentukan satu tipe pasti untuk keseluruhan ekspresi if, dan cabang yang bertipe berbeda membuat tipe hasil ambigu. Mari kita lihat compiler menolak cabang "genap" (string) yang dipasangkan dengan cabang 0 (integer):
fn main() {
let number = 7;
let parity = if number % 2 == 0 { "genap" } else { 0 };
println!("Angka {} adalah bilangan {}.", number, parity);
}Output yang Dihasilkan (galat kompilasi):
error[E0308]: `if` and `else` have incompatible types
--> main.rs:4:56
|
4 | let parity = if number % 2 == 0 { "genap" } else { 0 };
| ------- ^ expected `&str`, found integer
| |
| expected because of this
error: aborting due to 1 previous errorPesan galat menunjukkan dengan tepat cabang mana yang jadi acuan ("genap" bertipe &str) dan cabang mana yang menyimpang (0 bertipe integer). Perbaikannya memilih satu tipe untuk kedua cabang — misalnya "genap" dan "ganjil" seperti contoh pertama, atau 0 dan 1 bila kita memang menginginkan penanda angka.
Titik Koma Mengubah Hasil Blok #
Aturan main kedua menyangkut posisi titik koma. Nilai cabang ditentukan oleh ekspresi terakhir blok tanpa titik koma — menambahkan titik koma setelah ekspresi terakhir mengubah blok tersebut menjadi urutan statement yang menghasilkan tipe kosong (). Galat berikut muncul ketika kita lengah menambahkan titik koma di ujung kedua cabang:
fn main() {
let number = 7;
let parity = if number % 2 == 0 { "genap"; } else { "ganjil"; };
println!("Angka {} adalah bilangan {}.", number, parity);
}Output yang Dihasilkan (galat kompilasi):
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> main.rs:6:54
|
6 | println!("Angka {} adalah bilangan {}.", number, parity);
| -- ^^^^^^ `()` cannot be formatted with the default formatter
| |
| required by this formatting parameter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
error: aborting due to 1 previous errorGalatnya muncul agak jauh dari sumber masalah: titik koma membuat kedua cabang menghasilkan (), variabel parity pun bertipe (), dan baris println! yang mencoba mencetak parity gagal karena tipe () tidak mengimplementasikan trait Display. Kedua cabang kini memang bertipe sama (()), sehingga kompiler tidak lagi mengeluh soal ketidakcocokan tipe — masalahnya bergeser ke titik pemakaian nilainya. Menghapus kedua titik koma di dalam cabang mengembalikan program ke bentuk yang sehat pada contoh pertama.
Blok Biasa juga Ekspresi #
Perilaku “ekspresi terakhir tanpa titik koma menjadi nilai blok” bukan milik if saja — setiap blok kurung kurawal di Rust adalah ekspresi. Pemakaian praktisnya adalah membungkus perhitungan antara yang hanya relevan sementara, lalu menyerahkan hasil akhirnya ke variabel:
fn main() {
let price = 75000;
let quantity = 3;
let total = {
let subtotal = price * quantity;
let shipping = 10000;
subtotal + shipping
};
println!("Total belanja: Rp{}", total);
}Output yang Dihasilkan:
Total belanja: Rp235000Mari kita uraikan program di atas:
- Baris 5 (
let total = {): blok kurung kurawal dibuka tepat di posisi nilai pada statementlet; hasil blok akan diikat ketotal. - Baris 6 (
let subtotal = price * quantity;): perhitungan antara75000 * 3 = 225000disimpan ke variabelsubtotalyang hanya hidup di dalam blok. - Baris 7 (
let shipping = 10000;): variabelshippingjuga lokal untuk blok — keduanya tidak dapat diakses setelah blok berakhir. - Baris 8 (
subtotal + shipping): baris terakhir blok ditulis tanpa titik koma, sehingga ekspresi225000 + 10000menjadi nilai blok; variabeltotalmenerima235000. - Baris 9 (
};): titik koma setelah kurung kurawal penutup menutup statementlet total, sama seperti pola padalet ... = loopdanlet ... = match.
Konsep yang sama menyeragamkan tiga konstruksi yang sudah kita pelajari: loop dengan break nilai, match yang tiap cabangnya menyumbang nilai, dan if yang cabangnya menyumbang nilai — semuanya berdiri di atas aturan dasar bahwa blok adalah ekspresi. Memahami satu aturan ini memberi kita intuisi yang benar tentang seluruh bentuk-bentuk tersebut.
Ringkasan #
if di Rust adalah expression yang menghasilkan nilai dari cabang yang terpenuhi, sehingga let x = if kondisi { a } else { b }; menggantikan operator ternary yang tidak ada di Rust. Dua aturan menjaga kepastian tipe: kedua cabang harus menghasilkan tipe yang sama, dan ekspresi terakhir tiap cabang ditulis tanpa titik koma — titik koma mengubah hasil blok menjadi (). Aturan blok-sebagai-ekspresi juga berlaku pada blok biasa, loop, dan match, menjadikannya fondasi bersama seluruh bentuk pemilihan nilai di Rust. Pengetahuan ini melengkapi Section Kontrol Alur dan membekali kita untuk memahami fungsi serta nilai kembalian pada Section berikutnya.