পাঠ ৬.৩

if let এবং let...else দিয়ে concise control flow

Concise Control Flow with if let and let...else

match exhaustive এবং powerful, কিন্তু কখনো কখনো একটা মাত্র case-এর জন্য পুরো match লেখা verbose মনে হয়। এই পাঠে সংক্ষেপিত form দু'টা দেখব — if let এবং let...else

Verbose match — শুধু এক arm-এর জন্য

fn main() {
    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {max}"),
        _ => (),
    }
}

_ => () শুধু exhaustiveness satisfy করতে — কাজের কাজ কিছু না। এই boilerplate এড়ানো যায়।

if let — সংক্ষিপ্ত form

fn main() {
    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {max}");
    }
}

Syntax — if let pattern = expression {...}। Match-এর প্রথম arm হিসেবে ভাবতে পারো; pattern fit করলে block-এর code চলে, না হলে skip।

Trade-off: conciseness gain, কিন্তু exhaustiveness check হারালাম। কোনটা ভালো — situation-এর উপর depend করে।

if let + else

match-এর _ arm-এর equivalent হিসেবে else:

#[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;
    }
}

let...else — early return pattern

একটা common pattern — value extract কর; না পেলে early return। এটা if let দিয়ে লিখলে nested আকার নেয়:

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."))
    }
}

এই কাজটা cleaner হয় let...else-এ:

impl UsState {
    fn existed_in(&self, year: u16) -> bool {
        match self {
            UsState::Alabama => year >= 1819,
            UsState::Alaska => year >= 1959,
            // --snip--
        }
    }
}

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."))
    }
}

Syntax — pattern বাঁদিকে, expression ডানদিকে; pattern match হলে variable bind হয় outer scope-এ। Match না হলে else branch চলে — সেটা অবশ্যই diverge করতে হবে (return, break, panic, ইত্যাদি)। এর ফলে main "happy path" indentation-হীন থাকে।

কখন কোনটা?

  • match — সব variant-এর জন্য আলাদা logic; exhaustiveness দরকার।
  • if let — শুধু একটা specific pattern-এ কিছু করতে চাও।
  • if let ... else — দু'টা branch, একটা specific pattern, একটা default।
  • let...else — extract কর, না পেলে early return; happy path-কে indentation-হীন রাখো।

এই পাঠ থেকে যা শিখলে

  • if let pattern = expr {...} — match-এর single-arm shorthand।
  • if let ... else — দু'টা case-এর জন্য shortcut।
  • let pattern = expr else { return ... } — early-return pattern, happy path flat।