Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

if let এবং let else দিয়ে সংক্ষিপ্ত কন্ট্রোল ফ্লো

if let সিনট্যাক্সটি আপনাকে if এবং let-কে একত্রিত করে একটি কম শব্দবহুল উপায়ে এমন ভ্যালুগুলো হ্যান্ডেল করতে দেয় যা একটি প্যাটার্নের সাথে মেলে, এবং বাকিগুলোকে উপেক্ষা করে। লিস্টিং ৬-৬ এর প্রোগ্রামটি বিবেচনা করুন যা config_max ভেরিয়েবলের একটি Option<u8> মানের উপর ম্যাচ করে কিন্তু শুধুমাত্র তখনই কোড চালাতে চায় যখন মানটি Some ভ্যারিয়েন্ট হয়।

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

যদি মানটি Some হয়, আমরা প্যাটার্নে max ভেরিয়েবলের সাথে মানটি বাইন্ড করে Some ভ্যারিয়েন্টের ভিতরের মানটি প্রিন্ট করি। আমরা None ভ্যালু নিয়ে কিছু করতে চাই না। match এক্সপ্রেশনকে সন্তুষ্ট করার জন্য, আমাদের শুধুমাত্র একটি ভ্যারিয়েন্ট প্রসেস করার পরে _ => () যোগ করতে হয়, যা একটি বিরক্তিকর বাড়তি কোড।

এর পরিবর্তে, আমরা if let ব্যবহার করে এটি আরও সংক্ষিপ্তভাবে লিখতে পারি। নিচের কোডটি লিস্টিং ৬-৬ এর match-এর মতোই আচরণ করে:

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

`if let` সিনট্যাক্সটি একটি প্যাটার্ন এবং একটি এক্সপ্রেশন নেয় যা একটি সমান চিহ্ন দ্বারা পৃথক করা থাকে। এটি `match`-এর মতোই কাজ করে, যেখানে এক্সপ্রেশনটি `match`-কে দেওয়া হয় এবং প্যাটার্নটি তার প্রথম arm হয়। এই ক্ষেত্রে, প্যাটার্নটি হলো `Some(max)`, এবং `max` ভেরিয়েবলটি `Some`-এর ভিতরের মানের সাথে বাইন্ড হয়। আমরা তখন `if let` ব্লকের বডিতে `max` ব্যবহার করতে পারি, ঠিক যেমন আমরা সংশ্লিষ্ট `match` arm-এ `max` ব্যবহার করেছিলাম। `if let` ব্লকের কোডটি কেবল তখনই চলে যদি মানটি প্যাটার্নের সাথে মেলে।

`if let` ব্যবহার করার অর্থ হলো কম টাইপিং, কম ইনডেন্টেশন এবং কম বাড়তি কোড। তবে, আপনি `match`-এর সেই সম্পূর্ণ চেকিং (exhaustive checking) হারান যা নিশ্চিত করে যে আপনি কোনো কেস হ্যান্ডেল করতে ভুলে যাচ্ছেন না। `match` এবং `if let`-এর মধ্যে কোনটি বেছে নেবেন তা নির্ভর করে আপনার নির্দিষ্ট পরিস্থিতিতে আপনি কী করছেন এবং সংক্ষিপ্ততা পাওয়ার জন্য সম্পূর্ণ চেকিং হারানোর ট্রেড-অফটি উপযুক্ত কিনা তার উপর।

অন্য কথায়, আপনি `if let`-কে একটি `match`-এর জন্য সিনট্যাক্স সুগার (syntax sugar) হিসাবে ভাবতে পারেন যা মান একটি প্যাটার্নের সাথে মিললে কোড চালায় এবং তারপর অন্য সব মানকে উপেক্ষা করে।

আমরা `if let`-এর সাথে একটি `else` অন্তর্ভুক্ত করতে পারি। `else`-এর সাথে থাকা কোড ব্লকটি সেই কোড ব্লকের মতোই যা `_` কেসের সাথে যেত, যা `if let` এবং `else`-এর সমতুল্য `match` এক্সপ্রেশনে থাকতো। লিস্টিং ৬-৪-এর `Coin` enum সংজ্ঞাটি মনে করুন, যেখানে `Quarter` ভ্যারিয়েন্টটিতে একটি `UsState` মানও ছিল। যদি আমরা কোয়ার্টারগুলোর রাজ্যের নাম ঘোষণা করার পাশাপাশি দেখা সমস্ত নন-কোয়ার্টার কয়েন গণনা করতে চাই, আমরা এটি একটি `match` এক্সপ্রেশন দিয়ে করতে পারতাম, এভাবে:

```rust
#[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,
    }
}

অথবা আমরা একটি if let এবং 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 ব্যবহার করে “হ্যাপি পাথে” থাকা

একটি সাধারণ প্যাটার্ন হলো যখন একটি মান উপস্থিত থাকে তখন কিছু গণনা করা এবং অন্যথায় একটি ডিফল্ট মান ফেরত দেওয়া। আমাদের UsState মানসহ কয়েনের উদাহরণটি চালিয়ে গেলে, যদি আমরা কোয়ার্টারের উপর থাকা রাজ্যের বয়সের উপর নির্ভর করে মজার কিছু বলতে চাই, আমরা একটি রাজ্যের বয়স পরীক্ষা করার জন্য UsState-এ একটি মেথড যুক্ত করতে পারি, এভাবে:

#[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 ব্যবহার করতে পারি, কন্ডিশনের বডির মধ্যে একটি state ভেরিয়েবল চালু করে, যেমনটি লিস্টিং ৬-৭-এ দেখানো হয়েছে।

#[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 স্টেটমেন্টের বডির মধ্যে ঠেলে দিয়েছে, এবং যদি কাজটি আরও জটিল হয়, তবে টপ-লেভেল ব্রাঞ্চগুলো কীভাবে সম্পর্কিত তা বোঝা কঠিন হতে পারে। আমরা if let থেকে state তৈরি করতে বা আগেভাগেই রিটার্ন করতে এক্সপ্রেশনগুলো একটি মান তৈরি করে এই সত্যের সুবিধাও নিতে পারি, যেমনটি লিস্টিং ৬-৮-এ দেখানো হয়েছে। (আপনি 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-এর একটি ব্রাঞ্চ একটি মান তৈরি করে, এবং অন্যটি ফাংশন থেকে পুরোপুরি রিটার্ন করে।

এই সাধারণ প্যাটার্নটিকে আরও সুন্দরভাবে প্রকাশ করার জন্য, Rust-এ let...else রয়েছে। let...else সিনট্যাক্সটি বাম দিকে একটি প্যাটার্ন এবং ডানদিকে একটি এক্সপ্রেশন নেয়, যা if let-এর মতোই, তবে এটির কোনো if ব্রাঞ্চ নেই, কেবল একটি else ব্রাঞ্চ আছে। যদি প্যাটার্নটি মেলে, এটি বাইরের স্কোপে প্যাটার্ন থেকে মানটি বাইন্ড করবে। যদি প্যাটার্নটি না মেলে, প্রোগ্রামটি else arm-এ চলে যাবে, যা অবশ্যই ফাংশন থেকে রিটার্ন করতে হবে।

লিস্টিং ৬-৯-এ, আপনি দেখতে পারেন লিস্টিং ৬-৮ if let-এর পরিবর্তে let...else ব্যবহার করলে কেমন দেখায়।

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

লক্ষ্য করুন যে এটি ফাংশনের প্রধান বডিতে "হ্যাপি পাথে" (on the happy path) থাকে, if let-এর মতো দুটি ব্রাঞ্চের জন্য উল্লেখযোগ্যভাবে ভিন্ন কন্ট্রোল ফ্লো ছাড়াই।

যদি আপনার এমন পরিস্থিতি থাকে যেখানে আপনার প্রোগ্রামের যুক্তি একটি match ব্যবহার করে প্রকাশ করার জন্য খুব শব্দবহুল হয়, মনে রাখবেন যে if let এবং let...else আপনার Rust টুলবক্সেও রয়েছে।

সারাংশ

আমরা এখন কাস্টম type তৈরি করতে enum কীভাবে ব্যবহার করতে হয় তা কভার করেছি যা একটি গণনাকৃত মানের সেটের মধ্যে একটি হতে পারে। আমরা দেখিয়েছি কীভাবে স্ট্যান্ডার্ড লাইব্রেরির Option<T> type আপনাকে type সিস্টেম ব্যবহার করে error প্রতিরোধ করতে সহায়তা করে। যখন enum মানগুলোর ভিতরে ডেটা থাকে, আপনি match বা if let ব্যবহার করে সেই মানগুলো এক্সট্র্যাক্ট এবং ব্যবহার করতে পারেন, এটি নির্ভর করে আপনার কতগুলো কেস হ্যান্ডেল করতে হবে তার উপর।

আপনার Rust প্রোগ্রামগুলো এখন আপনার ডোমেনের ধারণাগুলো structs এবং enums ব্যবহার করে প্রকাশ করতে পারে। আপনার API-তে ব্যবহারের জন্য কাস্টম type তৈরি করা type safety নিশ্চিত করে: compiler নিশ্চিত করবে যে আপনার ফাংশনগুলো কেবল সেই type-এর মান পায় যা প্রতিটি ফাংশন আশা করে।

আপনার ব্যবহারকারীদের জন্য একটি সুসংগঠিত API সরবরাহ করার জন্য যা ব্যবহার করা সহজ এবং শুধুমাত্র ব্যবহারকারীদের যা প্রয়োজন তা প্রকাশ করে, আসুন এখন Rust-এর মডিউলগুলোর দিকে নজর দেওয়া যাক।