যে সব জায়গায় প্যাটার্ন ব্যবহার করা যায় (All the Places Patterns Can Be Used)
রাস্টের বিভিন্ন জায়গায় প্যাটার্ন ব্যবহার করা হয়, এবং আপনি হয়তো না জেনেই ইতোমধ্যেই এগুলো অনেকবার ব্যবহার করেছেন! এই সেকশনে সেই সব জায়গা নিয়ে আলোচনা করা হবে যেখানে প্যাটার্ন ব্যবহার করা বৈধ।
match
Arms
৬ষ্ঠ অধ্যায়ে যেমন আলোচনা করা হয়েছে, আমরা match
এক্সপ্রেশনের প্রতিটি arm-এ প্যাটার্ন ব্যবহার করি। আনুষ্ঠানিকভাবে, match
এক্সপ্রেশনকে এভাবে সংজ্ঞায়িত করা হয়: match
কীওয়ার্ড, যার উপর ম্যাচ করা হবে সেই ভ্যালু, এবং এক বা একাধিক ম্যাচ arm যা একটি প্যাটার্ন এবং একটি এক্সপ্রেশন নিয়ে গঠিত। যদি ভ্যালুটি সেই arm-এর প্যাটার্নের সাথে মিলে যায়, তবে এক্সপ্রেশনটি রান হয়, যেমন:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
উদাহরণস্বরূপ, এখানে লিস্টিং ৬-৫ থেকে match
এক্সপ্রেশনটি দেওয়া হলো যা x
ভেরিয়েবলের একটি Option<i32>
ভ্যালুর উপর ম্যাচ করে:
match x {
None => None,
Some(i) => Some(i + 1),
}
এই match
এক্সপ্রেশনের প্যাটার্নগুলো হলো প্রতিটি অ্যারো (=>
) চিহ্নের বাম দিকের None
এবং Some(i)
।
match
এক্সপ্রেশনের জন্য একটি আবশ্যিক শর্ত হলো সেগুলোকে এক্সহস্টিভ (exhaustive) বা সর্বগ্রাসী হতে হবে, অর্থাৎ match
এক্সপ্রেশনের ভ্যালুর জন্য সমস্ত সম্ভাব্য মান বিবেচনা করতে হবে। আপনি যে সমস্ত সম্ভাবনা কভার করেছেন তা নিশ্চিত করার একটি উপায় হলো শেষ arm-এর জন্য একটি ক্যাচ-অল (catch-all) প্যাটার্ন রাখা: উদাহরণস্বরূপ, যেকোনো মানের সাথে মেলে এমন একটি ভেরিয়েবলের নাম কখনও ব্যর্থ হতে পারে না এবং এভাবে বাকি সমস্ত কেইস কভার করে।
নির্দিষ্ট প্যাটার্ন _
যেকোনো কিছুর সাথে মিলবে, কিন্তু এটি কখনও কোনো ভেরিয়েবলের সাথে বাইন্ড হয় না, তাই এটি প্রায়শই শেষ ম্যাচ arm-এ ব্যবহৃত হয়। _
প্যাটার্নটি তখন কার্যকর হতে পারে যখন আপনি নির্দিষ্ট করা হয়নি এমন কোনো মান উপেক্ষা করতে চান। আমরা এই অধ্যায়ের পরে "একটি প্যাটার্নে মান উপেক্ষা করা" অংশে _
প্যাটার্ন সম্পর্কে আরও বিস্তারিত আলোচনা করব।
let
Statements
এই অধ্যায়ের আগে, আমরা কেবল match
এবং if let
-এর সাথে প্যাটার্ন ব্যবহার নিয়ে স্পষ্টভাবে আলোচনা করেছি, কিন্তু আসলে, আমরা let
স্টেটমেন্ট সহ অন্যান্য জায়গাতেও প্যাটার্ন ব্যবহার করেছি। উদাহরণস্বরূপ, let
দিয়ে এই সহজ ভেরিয়েবল অ্যাসাইনমেন্ট বিবেচনা করুন:
#![allow(unused)] fn main() { let x = 5; }
যতবার আপনি এই ধরনের একটি let
স্টেটমেন্ট ব্যবহার করেছেন, ততবার আপনি প্যাটার্ন ব্যবহার করেছেন, যদিও আপনি হয়তো তা বুঝতে পারেননি! আরও আনুষ্ঠানিকভাবে, একটি let
স্টেটমেন্ট দেখতে এইরকম:
let PATTERN = EXPRESSION;
let x = 5;
এর মতো স্টেটমেন্টে PATTERN স্লটে একটি ভেরিয়েবলের নাম থাকে, এই ভেরিয়েবলের নামটি আসলে প্যাটার্নের একটি অতি সরল রূপ। রাস্ট এক্সপ্রেশনটিকে প্যাটার্নের সাথে তুলনা করে এবং এটি যে নামগুলো খুঁজে পায় তা অ্যাসাইন করে। সুতরাং, let x = 5;
উদাহরণে, x
একটি প্যাটার্ন যার অর্থ "এখানে যা মিলবে তা x
ভেরিয়েবলে বাইন্ড করো"। যেহেতু x
নামটি পুরো প্যাটার্ন, তাই এই প্যাটার্নটির কার্যকর অর্থ হলো "মান যাই হোক না কেন, সবকিছু x
ভেরিয়েবলে বাইন্ড করো"।
let
-এর প্যাটার্ন-ম্যাচিং দিকটি আরও স্পষ্টভাবে দেখতে, লিস্টিং ১৯-১ বিবেচনা করুন, যা একটি টুপলকে ডিস্ট্রাকচার (destructure) বা ভাঙতে let
-এর সাথে একটি প্যাটার্ন ব্যবহার করে।
fn main() { let (x, y, z) = (1, 2, 3); }
এখানে, আমরা একটি টুপলকে একটি প্যাটার্নের সাথে ম্যাচ করাই। রাস্ট (1, 2, 3)
ভ্যালুটিকে (x, y, z)
প্যাটার্নের সাথে তুলনা করে এবং দেখে যে ভ্যালুটি প্যাটার্নের সাথে মিলেছে, কারণ এটি দেখে যে দুটিতেই উপাদানের সংখ্যা সমান, তাই রাস্ট 1
-কে x
-এ, 2
-কে y
-তে এবং 3
-কে z
-এ বাইন্ড করে। আপনি এই টুপল প্যাটার্নটিকে তিনটি পৃথক ভেরিয়েবল প্যাটার্নের নেস্টেড রূপ হিসাবে ভাবতে পারেন।
যদি প্যাটার্নের উপাদানের সংখ্যা টুপলের উপাদানের সংখ্যার সাথে না মেলে, তাহলে সামগ্রিক টাইপ মিলবে না এবং আমরা একটি কম্পাইলার এরর পাব। উদাহরণস্বরূপ, লিস্টিং ১৯-২ দেখায় তিনটি উপাদান সহ একটি টুপলকে দুটি ভেরিয়েবলে ডিস্ট্রাকচার করার একটি প্রচেষ্টা, যা কাজ করবে না।
fn main() {
let (x, y) = (1, 2, 3);
}
এই কোডটি কম্পাইল করার চেষ্টা করলে এই টাইপ এররটি ঘটে:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
এররটি ঠিক করতে, আমরা টুপলের এক বা একাধিক মান উপেক্ষা করতে _
বা ..
ব্যবহার করতে পারতাম, যেমনটি আপনি "একটি প্যাটার্নে মান উপেক্ষা করা" সেকশনে দেখবেন। যদি সমস্যাটি প্যাটার্নে অনেক বেশি ভেরিয়েবল থাকা হয়, তবে সমাধান হলো ভেরিয়েবল সরিয়ে টাইপগুলো মেলানো যাতে ভেরিয়েবলের সংখ্যা টুপলের উপাদানের সংখ্যার সমান হয়।
Conditional if let
Expressions
৬ষ্ঠ অধ্যায়ে, আমরা if let
এক্সপ্রেশন নিয়ে আলোচনা করেছি, যা মূলত একটি match
-এর সংক্ষিপ্ত রূপ যা কেবল একটি কেইস ম্যাচ করে। ঐচ্ছিকভাবে, if let
-এর একটি সংশ্লিষ্ট else
থাকতে পারে, যেখানে if let
-এর প্যাটার্নটি না মিললে রান করার জন্য কোড থাকে।
লিস্টিং ১৯-৩ দেখায় যে if let
, else if
, এবং else if let
এক্সপ্রেশনগুলো মিশ্রিত করা এবং মেলানোও সম্ভব। এটি আমাদের match
এক্সপ্রেশনের চেয়ে বেশি নমনীয়তা দেয় যেখানে আমরা প্যাটার্নগুলোর সাথে তুলনা করার জন্য কেবল একটি মান প্রকাশ করতে পারি। এছাড়াও, রাস্টের জন্য এটি আবশ্যক নয় যে if let
, else if
, এবং else if let
arm-এর একটি সিরিজের শর্তাবলী একে অপরের সাথে সম্পর্কিত হতে হবে।
লিস্টিং ১৯-৩-এর কোডটি বেশ কয়েকটি শর্ত পরীক্ষা করে আপনার ব্যাকগ্রাউন্ডের জন্য কোন রঙ তৈরি করতে হবে তা নির্ধারণ করে। এই উদাহরণের জন্য, আমরা হার্ডকোডেড মান সহ ভেরিয়েবল তৈরি করেছি যা একটি বাস্তব প্রোগ্রাম ব্যবহারকারীর ইনপুট থেকে পেতে পারে।
fn main() { let favorite_color: Option<&str> = None; let is_tuesday = false; let age: Result<u8, _> = "34".parse(); if let Some(color) = favorite_color { println!("Using your favorite color, {color}, as the background"); } else if is_tuesday { println!("Tuesday is green day!"); } else if let Ok(age) = age { if age > 30 { println!("Using purple as the background color"); } else { println!("Using orange as the background color"); } } else { println!("Using blue as the background color"); } }
যদি ব্যবহারকারী একটি প্রিয় রঙ নির্দিষ্ট করে, তবে সেই রঙটি ব্যাকগ্রাউন্ড হিসাবে ব্যবহৃত হয়। যদি কোনো প্রিয় রঙ নির্দিষ্ট না করা হয় এবং আজ মঙ্গলবার হয়, তবে ব্যাকগ্রাউন্ডের রঙ সবুজ হবে। অন্যথায়, যদি ব্যবহারকারী তাদের বয়স একটি স্ট্রিং হিসাবে নির্দিষ্ট করে এবং আমরা সফলভাবে এটিকে একটি সংখ্যা হিসাবে পার্স করতে পারি, তবে সংখ্যার মানের উপর নির্ভর করে রঙটি বেগুনি বা কমলা হবে। যদি এই শর্তগুলোর কোনোটিই প্রযোজ্য না হয়, তবে ব্যাকগ্রাউন্ডের রঙ নীল হবে।
এই শর্তাধীন কাঠামোটি আমাদের জটিল প্রয়োজনীয়তা সমর্থন করতে দেয়। এখানে আমাদের হার্ডকোডেড মান দিয়ে, এই উদাহরণটি Using purple as the background color
প্রিন্ট করবে।
আপনি দেখতে পাচ্ছেন যে if let
নতুন ভেরিয়েবলও প্রবর্তন করতে পারে যা বিদ্যমান ভেরিয়েবলকে শ্যাডো (shadow) করে, ঠিক যেমন match
arm করতে পারে: if let Ok(age) = age
লাইনটি একটি নতুন age
ভেরিয়েবল প্রবর্তন করে যা Ok
ভ্যারিয়েন্টের ভিতরের মান ধারণ করে, বিদ্যমান age
ভেরিয়েবলটিকে শ্যাডো করে। এর মানে হলো আমাদের if age > 30
শর্তটি সেই ব্লকের মধ্যে রাখতে হবে: আমরা এই দুটি শর্তকে if let Ok(age) = age && age > 30
-তে একত্রিত করতে পারি না। নতুন age
যা আমরা 30-এর সাথে তুলনা করতে চাই তা নতুন স্কোপ কার্লি ব্র্যাকেট দিয়ে শুরু না হওয়া পর্যন্ত বৈধ নয়।
if let
এক্সপ্রেশন ব্যবহারের অসুবিধা হলো কম্পাইলার এক্সহস্টিভনেস বা সর্বগ্রাসীতার জন্য পরীক্ষা করে না, যেখানে match
এক্সপ্রেশনের ক্ষেত্রে তা করে। যদি আমরা শেষ else
ব্লকটি বাদ দিতাম এবং তাই কিছু কেইস হ্যান্ডেল করতে মিস করতাম, কম্পাইলার আমাদের সম্ভাব্য লজিক বাগ সম্পর্কে সতর্ক করত না।
while let
Conditional Loops
if let
-এর মতো গঠনে, while let
কন্ডিশনাল লুপ একটি while
লুপকে ততক্ষণ চলতে দেয় যতক্ষণ একটি প্যাটার্ন মিলতে থাকে। লিস্টিং ১৯-৪-এ আমরা একটি while let
লুপ দেখাই যা থ্রেডগুলোর মধ্যে পাঠানো মেসেজের জন্য অপেক্ষা করে, কিন্তু এক্ষেত্রে Option
-এর পরিবর্তে একটি Result
পরীক্ষা করে।
fn main() { let (tx, rx) = std::sync::mpsc::channel(); std::thread::spawn(move || { for val in [1, 2, 3] { tx.send(val).unwrap(); } }); while let Ok(value) = rx.recv() { println!("{value}"); } }
এই উদাহরণটি 1
, 2
, এবং তারপর 3
প্রিন্ট করে। recv
মেথডটি চ্যানেলের রিসিভার সাইড থেকে প্রথম মেসেজটি নেয় এবং একটি Ok(value)
রিটার্ন করে। যখন আমরা প্রথমবার ১৬ অধ্যায়ে recv
দেখেছিলাম, আমরা সরাসরি এররটি আনর্যাপ করেছিলাম, অথবা for
লুপ ব্যবহার করে একটি ইটারেটর হিসাবে এর সাথে ইন্টারঅ্যাক্ট করেছিলাম। তবে, লিস্টিং ১৯-৪ যেমন দেখাচ্ছে, আমরা while let
ব্যবহার করতে পারি, কারণ recv
মেথডটি প্রতিবার একটি মেসেজ আসলে একটি Ok
রিটার্ন করে, যতক্ষণ প্রেরক বিদ্যমান থাকে, এবং প্রেরক পক্ষ সংযোগ বিচ্ছিন্ন হয়ে গেলে একটি Err
তৈরি করে।
for
Loops
একটি for
লুপে, for
কীওয়ার্ডের সরাসরি পরে যে ভ্যালুটি আসে তা একটি প্যাটার্ন। উদাহরণস্বরূপ, for x in y
-তে, x
হলো প্যাটার্ন। লিস্টিং ১৯-৫ দেখায় কিভাবে for
লুপের অংশ হিসেবে একটি টুপলকে ডিস্ট্রাকচার বা ভাঙার জন্য for
লুপে একটি প্যাটার্ন ব্যবহার করতে হয়।
fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{value} is at index {index}"); } }
লিস্টিং ১৯-৫-এর কোডটি নিম্নলিখিত আউটপুট প্রিন্ট করবে:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
আমরা enumerate
মেথড ব্যবহার করে একটি ইটারেটরকে অভিযোজিত করি যাতে এটি একটি মান এবং সেই মানের জন্য সূচক (index) তৈরি করে, যা একটি টুপলে রাখা হয়। প্রথম উৎপাদিত মানটি হলো টুপল (0, 'a')
। যখন এই মানটি (index, value)
প্যাটার্নের সাথে ম্যাচ করানো হয়, তখন index
হবে 0
এবং value
হবে 'a'
, যা আউটপুটের প্রথম লাইনটি প্রিন্ট করে।
Function Parameters
ফাংশনের প্যারামিটারগুলোও প্যাটার্ন হতে পারে। লিস্টিং ১৯-৬-এর কোড, যা foo
নামের একটি ফাংশন ঘোষণা করে যা i32
টাইপের x
নামের একটি প্যারামিটার নেয়, এতক্ষণে পরিচিত মনে হওয়া উচিত।
fn foo(x: i32) { // code goes here } fn main() {}
x
অংশটি একটি প্যাটার্ন! যেমন আমরা let
-এর সাথে করেছি, আমরা একটি ফাংশনের আর্গুমেন্টে একটি টুপলকে প্যাটার্নের সাথে ম্যাচ করতে পারি। লিস্টিং ১৯-৭ একটি টুপলের মানগুলোকে ফাংশনে পাস করার সময় বিভক্ত করে।
fn print_coordinates(&(x, y): &(i32, i32)) { println!("Current location: ({x}, {y})"); } fn main() { let point = (3, 5); print_coordinates(&point); }
এই কোডটি Current location: (3, 5)
প্রিন্ট করে। &(3, 5)
মানগুলো &(x, y)
প্যাটার্নের সাথে মেলে, তাই x
-এর মান 3
এবং y
-এর মান 5
হয়।
আমরা ফাংশন প্যারামিটার তালিকার মতো একইভাবে ক্লোজার প্যারামিটার তালিকাতেও প্যাটার্ন ব্যবহার করতে পারি কারণ ক্লোজারগুলো ফাংশনের মতোই, যেমনটি ১৩ অধ্যায়ে আলোচনা করা হয়েছে।
এই মুহুর্তে, আপনি প্যাটার্ন ব্যবহার করার বেশ কয়েকটি উপায় দেখেছেন, কিন্তু প্যাটার্নগুলো আমরা যে সব জায়গায় ব্যবহার করতে পারি সেখানে একইভাবে কাজ করে না। কিছু জায়গায়, প্যাটার্নগুলো অবশ্যই অখণ্ডনযোগ্য (irrefutable) হতে হবে; অন্য পরিস্থিতিতে, তারা খণ্ডনযোগ্য (refutable) হতে পারে। আমরা এই দুটি ধারণা নিয়ে পরবর্তীতে আলোচনা করব।