Control Flow yang Ringkas pake if let sama let else
Sintaks if let ngebolehin kita ngegabungin if sama let jadi cara yang lebih
ringkas buat nge-handle nilai yang cocok sama satu pattern sambil nyuekin sisanya.
Coba liat program di Listing 6-6 yang nge-match nilai Option<u8> di variabel
config_max tapi cuma mau ngejalanin kode kalau nilainya itu varian Some.
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
}
}
match yang cuma peduli buat ngejalanin kode pas nilainya SomeKalau nilainya Some, kita nyetak nilainya di varian Some dengan nge-bind
nilai itu ke variabel max di dalem pattern-nya. Kita nggak mau ngelakuin apa-
apa sama nilai None. Buat menuhin syarat ekspresi match, kita harus nambahin
_ => () setelah memproses cuma satu varian, yang mana ini lumayan nyebelin
karena jadi boilerplate code yang harus ditambahin.
Sebagai gantinya, kita bisa nulis ini dengan cara yang lebih singkat pake if let.
Kode berikut perilakunya sama persis kayak match di Listing 6-6:
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}
}
Sintaks if let nerima sebuah pattern sama sebuah ekspresi yang dipisahin sama
tanda sama dengan. Dia cara kerjanya sama kayak sebuah match, di mana
ekspresinya dikasih ke match dan pattern-nya itu adalah arm pertamanya.
Di kasus ini, pattern-nya adalah Some(max), dan max di-bind ke nilai di dalem
Some. Terus kita bisa pake max di dalem body blok if let dengan cara yang
sama kayak kita pake max di arm match yang terkait. Kode di dalem blok if let
cuma jalan kalau nilainya cocok sama pattern-nya.
Pake if let artinya lebih dikit ngetik, lebih dikit indentasi, dan lebih dikit
boilerplate code. Tapi, kita kehilangan pengecekan exhaustive (menyeluruh)
yang diterapin sama match yang mastiin kalau kita nggak lupa nge-handle kasus
apa pun. Milih antara match sama if let tergantung dari apa yang lagi kita
lakuin di situasi kita saat itu dan apakah dapet keringkasan itu sebuah trade-off
yang pas buat ngorbanin pengecekan menyeluruh.
Dengan kata lain, kita bisa mikirin if let sebagai syntax sugar buat sebuah
match yang ngejalanin kode pas nilainya cocok sama satu pattern terus nyuekin
semua nilai lainnya.
Kita bisa masukin sebuah else barengan sama if let. Blok kode yang ngikutin
else itu sama kayak blok kode yang ngikutin kasus _ di ekspresi match
yang setara sama if let dan else itu. Inget definisi enum Coin di
Listing 6-4, di mana varian Quarter juga nyimpen nilai UsState. Kalau kita
mau ngitung semua koin yang bukan quarter sambil nyebutin negara bagian dari si
quarter, kita bisa lakuin itu pake ekspresi match, kayak gini:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn main() {
let coin = Coin::Penny;
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
_ => count += 1,
}
}
Atau kita bisa pake ekspresi if let dan else, kayak gini:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn main() {
let coin = Coin::Penny;
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {state:?}!");
} else {
count += 1;
}
}
Tetep di Jalur Aman (“Happy Path”) pake let...else
Pola yang umum adalah ngelakuin sebuah komputasi pas sebuah nilai ada isinya
dan balikin nilai default kalau sebaliknya. Lanjut pake contoh kita soal koin
dengan nilai UsState, kalau kita mau ngomong sesuatu yang lucu tergantung
seberapa tua negara bagian di koin quarter itu, kita mungkin bakal nambahin
sebuah method di UsState buat nge-cek umur sebuah negara bagian, kayak gini:
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
if let Coin::Quarter(state) = coin {
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
} else {
None
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
Terus kita mungkin pake if let buat nge-match tipe koinnya, ngenalin variabel
state di dalem body kondisinya, kayak di Listing 6-7.
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
if let Coin::Quarter(state) = coin {
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
} else {
None
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
if let.Emang beres sih kerjaannya, tapi ini ngegeser kerjaannya ke dalem body
statement if let, dan kalau kerjaan yang harus dilakuin lebih ribet, bakal
susah buat ngikutin persis gimana cabang-cabang top-level (tingkat atas)-nya
berhubungan. Kita juga bisa manfaatin fakta kalau ekspresi ngasilin nilai buat
ngasilin state dari if let atau return early (kembali lebih awal), kayak
di Listing 6-8. (Kita juga bisa ngelakuin hal yang mirip pake match.)
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
let state = if let Coin::Quarter(state) = coin {
state
} else {
return None;
};
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
if let buat ngasilin sebuah nilai atau return early.Tapi ini agak nyebelin buat diikutin dengan caranya sendiri! Satu cabang dari
if let ngasilin nilai, dan yang satunya return dari fungsi sepenuhnya.
Buat bikin pola umum ini lebih enak buat diekspresikan, Rust punya let...else.
Sintaks let...else nerima sebuah pattern di sisi kiri dan sebuah ekspresi di
sisi kanan, mirip sekali sama if let, tapi dia nggak punya cabang if, cuma
cabang else. Kalau pattern-nya cocok, dia bakal nge-bind nilai dari pattern
ke scope luar. Kalau pattern-nya nggak cocok, programnya bakal ngalir ke
dalem arm else, yang harus return (kembali) dari fungsinya.
Di Listing 6-9, kita bisa liat gimana penampakan Listing 6-8 pas pake let...else
sebagai ganti dari if let.
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
let Coin::Quarter(state) = coin else {
return None;
};
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
let...else buat ngejelasin alur (flow) lewat fungsinya.Perhatiin ya kalau dia tetep “on the happy path” (di jalur aman yang diharapkan)
di body utama fungsinya pake cara ini, tanpa harus punya alur kontrol yang
bener-bener beda jauh buat dua cabang kayak yang dilakuin sama if let.
Kalau kita ada di situasi di mana program kita punya logika yang terlalu panjang
(verbose) buat diekspresikan pake match, inget ya kalau if let sama
let...else juga ada di dalem toolbox Rust kita.
Ringkasan
Kita sekarang udah ngebahas gimana cara pake enum buat bikin tipe kustom yang
bisa jadi salah satu dari sekumpulan nilai yang di-enumerate. Kita udah
nunjukin gimana tipe Option<T> bawaan standard library ngebantu kita pake
sistem tipe buat nyegah error. Pas nilai enum punya data di dalemnya, kita bisa
pake match atau if let buat ngekstrak dan pake nilai-nilai itu, tergantung
dari seberapa banyak kasus yang perlu kita handle.
Program Rust kita sekarang bisa mengekspresikan konsep di domain kita pake struct dan enum. Bikin tipe kustom buat dipake di API kita mastiin keamanan tipe (type safety): compiler bakal mastiin fungsi kita cuma dapet nilai dari tipe yang diharapkan sama tiap fungsinya.
Buat nyediain API yang terorganisir dengan baik ke user kita yang gampang buat dipake dan cuma nge-ekspos tepat apa yang dibutuhin sama user kita aja, yuk sekarang kita beralih ke modules (modul) di Rust.