if let
এবং let else
দিয়ে সংক্ষিপ্ত কন্ট্রোল ফ্লো (Concise Control Flow with if let
and let else
)
if let
সিনট্যাক্স আপনাকে if
এবং let
-কে একত্রিত করে কম শব্দ ব্যবহার করে এমন মানগুলো হ্যান্ডেল করতে দেয়, যেগুলো একটি প্যাটার্নের সাথে মেলে এবং বাকিগুলো উপেক্ষা করে। Listing 6-6-এর প্রোগ্রামটি বিবেচনা করুন, যেটি 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
ব্যবহার করে এটিকে আরও সংক্ষিপ্তভাবে লিখতে পারি। নিম্নলিখিত কোডটি Listing 6-6-এর 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
-কে দেওয়া হয় এবং প্যাটার্নটি হল এর প্রথম আর্ম। এই ক্ষেত্রে, প্যাটার্নটি হল Some(max)
, এবং max
Some
-এর ভিতরের মানের সাথে বাইন্ড করে। তারপর আমরা if let
ব্লকের বডিতে max
ব্যবহার করতে পারি, একইভাবে আমরা সংশ্লিষ্ট match
আর্মে max
ব্যবহার করেছি। if let
ব্লকের কোড শুধুমাত্র তখনই চলে যদি মানটি প্যাটার্নের সাথে মেলে।
if let
ব্যবহার করার অর্থ হল কম টাইপিং, কম ইন্ডেন্টেশন এবং কম বয়লারপ্লেট কোড। তবে, আপনি match
যে এক্সহস্টিভ চেকিং (exhaustive checking) প্রয়োগ করে সেটি হারাবেন। match
এবং if let
-এর মধ্যে বেছে নেওয়া আপনার নির্দিষ্ট পরিস্থিতিতে আপনি কী করছেন এবং এক্সহস্টিভ চেকিং হারানো সংক্ষিপ্ততা অর্জনের জন্য উপযুক্ত কিনা তার উপর নির্ভর করে।
অন্য কথায়, আপনি if let
-কে একটি match
-এর সিনট্যাক্স সুগার হিসাবে ভাবতে পারেন যা মানটি একটি প্যাটার্নের সাথে মিললে কোড চালায় এবং তারপর অন্য সমস্ত মান উপেক্ষা করে।
আমরা if let
-এর সাথে একটি else
অন্তর্ভুক্ত করতে পারি। else
-এর সাথে থাকা কোডের ব্লকটি match
এক্সপ্রেশনের _
কেসের সাথে থাকা কোড ব্লকের মতোই, যেটি if let
এবং else
-এর সমতুল্য। Listing 6-4-এর Coin
এনামের সংজ্ঞাটি স্মরণ করুন, যেখানে Quarter
ভেরিয়েন্টটিতে একটি UsState
মানও ছিল। আমরা যদি কোয়ার্টারগুলোর রাজ্যের ঘোষণা করার পাশাপাশি সমস্ত নন-কোয়ার্টার কয়েন গণনা করতে চাই, তাহলে আমরা এটি একটি match
এক্সপ্রেশন দিয়ে করতে পারি, এইভাবে:
#[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
দিয়ে ("Staying on the “happy path” with 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
ব্যবহার করতে পারি, Listing 6-7-এর মতো, কন্ডিশনের বডিতে একটি 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
তৈরি করতে বা তাড়াতাড়ি রিটার্ন করতে, যেমনটি Listing 6-8-এ রয়েছে। (আপনি একটি 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
আর্মের মধ্যে চলে যাবে, যেটিকে অবশ্যই ফাংশন থেকে রিটার্ন করতে হবে।
Listing 6-9-এ, আপনি দেখতে পারেন কিভাবে Listing 6-8 if let
-এর পরিবর্তে let
-else
ব্যবহার করলে কেমন দেখায়। লক্ষ্য করুন যে এটি ফাংশনের মূল বডিতে "হ্যাপি পাথে" থাকে, দুটি শাখার জন্য উল্লেখযোগ্যভাবে ভিন্ন কন্ট্রোল ফ্লো না রেখে যেভাবে 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}"); } }
যদি আপনার এমন পরিস্থিতি থাকে যেখানে আপনার প্রোগ্রামের লজিক একটি match
ব্যবহার করে প্রকাশ করা খুব শব্দবহুল হয়, তাহলে মনে রাখবেন যে if let
এবং let else
আপনার Rust টুলবক্সে রয়েছে।
সারসংক্ষেপ (Summary)
আমরা এখন এনামগুলো ব্যবহার করে কীভাবে কাস্টম টাইপ তৈরি করতে হয় তা কভার করেছি, যেগুলো গণনাকৃত মানগুলোর একটি সেটের মধ্যে একটি হতে পারে। আমরা দেখেছি কিভাবে স্ট্যান্ডার্ড লাইব্রেরির Option<T>
টাইপ আপনাকে এরর প্রতিরোধ করতে টাইপ সিস্টেম ব্যবহার করতে সাহায্য করে। যখন এনাম মানগুলোর ভিতরে ডেটা থাকে, তখন আপনি কতগুলো ক্ষেত্র হ্যান্ডেল করতে হবে তার উপর নির্ভর করে সেই মানগুলো বের করতে এবং ব্যবহার করতে match
বা if let
ব্যবহার করতে পারেন।
আপনার Rust প্রোগ্রামগুলো এখন স্ট্রাকট এবং এনাম ব্যবহার করে আপনার ডোমেনে ধারণাগুলো প্রকাশ করতে পারে। আপনার API-তে ব্যবহার করার জন্য কাস্টম টাইপ তৈরি করা টাইপ নিরাপত্তা নিশ্চিত করে: কম্পাইলার নিশ্চিত করবে যে আপনার ফাংশনগুলো শুধুমাত্র সেই টাইপের মানগুলো পাবে যা প্রতিটি ফাংশন আশা করে।
আপনার ব্যবহারকারীদের কাছে একটি সুসংগঠিত API সরবরাহ করার জন্য যা ব্যবহার করা সহজ এবং শুধুমাত্র আপনার ব্যবহারকারীদের যা প্রয়োজন সেটাই প্রকাশ করে, আসুন এবার Rust-এর মডিউলগুলোর দিকে যাই।