যেখানে যেখানে প্যাটার্ন ব্যবহার করা যেতে পারে (All the Places Patterns Can Be Used)

Rust-এ প্যাটার্নগুলি বেশ কয়েকটি জায়গায় ব্যবহৃত হয়, এবং আপনি না জেনেই সেগুলি অনেক ব্যবহার করেছেন! এই বিভাগে সেই সমস্ত স্থান নিয়ে আলোচনা করা হয়েছে যেখানে প্যাটার্ন বৈধ।

match আর্ম (Arms)

Chapter 6-এ আলোচনা করা হয়েছে, আমরা match এক্সপ্রেশনের আর্ম-এ প্যাটার্ন ব্যবহার করি। আনুষ্ঠানিকভাবে, match এক্সপ্রেশনগুলিকে match কীওয়ার্ড, ম্যাচ করার জন্য একটি মান এবং এক বা একাধিক ম্যাচ আর্ম হিসাবে সংজ্ঞায়িত করা হয় যা একটি প্যাটার্ন এবং মানটি সেই আর্মের প্যাটার্নের সাথে মিললে চালানোর জন্য একটি এক্সপ্রেশন নিয়ে গঠিত, এইরকম:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

উদাহরণস্বরূপ, Listing 6-5 থেকে match এক্সপ্রেশনটি এখানে দেওয়া হল, যা x ভেরিয়েবলের একটি Option<i32> মানের উপর ম্যাচ করে:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

এই match এক্সপ্রেশনের প্যাটার্নগুলি হল প্রতিটি তীর চিহ্নের বাম দিকের None এবং Some(i)

match এক্সপ্রেশনের একটি প্রয়োজনীয়তা হল যে মানটির জন্য match এক্সপ্রেশন ব্যবহার করা হচ্ছে, তার সমস্ত সম্ভাবনা অবশ্যই বিবেচনায় রাখতে হবে। এটিকে এক্সহসটিভ (exhaustive) হতে হবে। প্রতিটি সম্ভাবনা কভার করা হয়েছে তা নিশ্চিত করার একটি উপায় হল শেষ আর্মের জন্য একটি ক্যাচ-অল প্যাটার্ন থাকা: উদাহরণস্বরূপ, যেকোনো মানের সাথে মেলে এমন একটি ভেরিয়েবলের নাম কখনই ব্যর্থ হতে পারে না এবং এইভাবে প্রতিটি অবশিষ্ট কেস কভার করে।

_ নামক বিশেষ প্যাটার্নটি যেকোনো কিছুর সাথে মিলবে, কিন্তু এটি কখনই কোনো ভেরিয়েবলের সাথে বাইন্ড করে না, তাই এটি প্রায়শই শেষ ম্যাচ আর্মে ব্যবহৃত হয়। _ প্যাটার্নটি দরকারী হতে পারে যখন আপনি নির্দিষ্ট করা হয়নি এমন কোনো মান উপেক্ষা করতে চান। আমরা এই চ্যাপ্টারের পরে “Ignoring Values in a Pattern” বিভাগে _ প্যাটার্নটি আরও বিশদে কভার করব।

কন্ডিশনাল if let এক্সপ্রেশন (Conditional if let Expressions)

Chapter 6-এ আমরা আলোচনা করেছি কিভাবে if let এক্সপ্রেশনগুলিকে মূলত একটি match-এর সমতুল্য লেখার সংক্ষিপ্ত উপায় হিসাবে ব্যবহার করতে হয় যা শুধুমাত্র একটি কেসের সাথে মেলে। ঐচ্ছিকভাবে, if let-এ একটি সংশ্লিষ্ট else থাকতে পারে যাতে কোড চালানোর জন্য থাকে যদি if let-এর প্যাটার্নটি না মেলে।

Listing 19-1 দেখায় যে if let, else if, এবং else if let এক্সপ্রেশনগুলিকে মিশ্রিত করা এবং মেলানোও সম্ভব। এটি আমাদের একটি match এক্সপ্রেশনের চেয়ে বেশি নমনীয়তা দেয় যেখানে আমরা প্যাটার্নগুলির সাথে তুলনা করার জন্য শুধুমাত্র একটি মান প্রকাশ করতে পারি। এছাড়াও, Rust-এর প্রয়োজন নেই যে if let, else if, else if let আর্মের একটি সিরিজের শর্তগুলি একে অপরের সাথে সম্পর্কিত হোক।

Listing 19-1-এর কোডটি বেশ কয়েকটি শর্তের জন্য একটি সিরিজের চেকের উপর ভিত্তি করে আপনার ব্যাকগ্রাউন্ডের রং কী হবে তা নির্ধারণ করে। এই উদাহরণের জন্য, আমরা হার্ডকোডেড মান সহ ভেরিয়েবল তৈরি করেছি যা একটি বাস্তব প্রোগ্রাম ব্যবহারকারীর ইনপুট থেকে পেতে পারে।

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 নতুন ভেরিয়েবলও প্রবর্তন করতে পারে যা বিদ্যমান ভেরিয়েবলগুলিকে শ্যাডো করে, একইভাবে যেভাবে match আর্মগুলি পারে: 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 কন্ডিশনাল লুপ (while let Conditional Loops)

if let-এর গঠনের অনুরূপ, while let কন্ডিশনাল লুপ একটি while লুপকে ততক্ষণ চলতে দেয় যতক্ষণ একটি প্যাটার্ন মিলতে থাকে। আমরা প্রথমবার Chapter 17-এ একটি while let লুপ দেখেছিলাম, যেখানে আমরা এটিকে ততক্ষণ লুপ করতে ব্যবহার করেছি যতক্ষণ একটি স্ট্রিম নতুন মান তৈরি করে। একইভাবে, Listing 19-2-তে আমরা একটি 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 প্রিন্ট করে। যখন আমরা Chapter 16-এ recv দেখেছিলাম, তখন আমরা সরাসরি error টি আনর‍্যাপ করেছি, অথবা একটি for লুপ ব্যবহার করে একটি ইটারেটর হিসাবে এটির সাথে ইন্টারঅ্যাক্ট করেছি। Listing 19-2 যেমন দেখায়, যদিও, আমরা while let ব্যবহার করতে পারি, কারণ recv মেথডটি যতক্ষণ সেন্ডার মেসেজ তৈরি করছে ততক্ষণ Ok রিটার্ন করে এবং তারপর সেন্ডার সাইড ডিসকানেক্ট হয়ে গেলে একটি Err তৈরি করে।

for লুপ (for Loops)

একটি for লুপে, for কীওয়ার্ডের ঠিক পরে যে মানটি আসে সেটি হল একটি প্যাটার্ন। উদাহরণস্বরূপ, for x in y-তে x হল প্যাটার্ন। Listing 19-3 প্রদর্শন করে কিভাবে একটি for লুপে একটি প্যাটার্ন ব্যবহার করে একটি টাপলকে ডিস্ট্রাকচার বা ভেঙে আলাদা করা যায়, for লুপের অংশ হিসাবে।

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}

Listing 19-3-এর কোডটি নিম্নলিখিতগুলি প্রিন্ট করবে:

$ 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 মেথড ব্যবহার করে একটি ইটারেটরকে অ্যাডাপ্ট করি যাতে এটি একটি মান এবং সেই মানের জন্য ইনডেক্স তৈরি করে, একটি টাপলে স্থাপন করা হয়। উৎপাদিত প্রথম মান হল (0, 'a') টাপল। যখন এই মানটি (index, value) প্যাটার্নের সাথে মেলানো হয়, তখন index হবে 0 এবং value হবে 'a', আউটপুটের প্রথম লাইনটি প্রিন্ট করবে।

let স্টেটমেন্ট (let Statements)

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

#![allow(unused)]
fn main() {
let x = 5;
}

আপনি যখনই এইরকম একটি let স্টেটমেন্ট ব্যবহার করেছেন তখনই আপনি প্যাটার্ন ব্যবহার করছেন, যদিও আপনি এটি উপলব্ধি নাও করতে পারেন! আরও আনুষ্ঠানিকভাবে, একটি let স্টেটমেন্ট এইরকম দেখায়:

let PATTERN = EXPRESSION;

let x = 5;-এর মতো স্টেটমেন্টে PATTERN স্লটে একটি ভেরিয়েবলের নাম সহ, ভেরিয়েবলের নামটি কেবল একটি প্যাটার্নের একটি বিশেষ সরল রূপ। Rust এক্সপ্রেশনটিকে প্যাটার্নের সাথে তুলনা করে এবং যে কোনও নাম খুঁজে পায় তা অ্যাসাইন করে। তাই let x = 5; উদাহরণে, x হল একটি প্যাটার্ন যার অর্থ “এখানে যা মেলে তাকে x ভেরিয়েবলের সাথে বাইন্ড করুন।” যেহেতু x নামটি সম্পূর্ণ প্যাটার্ন, তাই এই প্যাটার্নটির কার্যকরী অর্থ হল “মান যাই হোক না কেন, সবকিছুকে x ভেরিয়েবলের সাথে বাইন্ড করুন।”

let-এর প্যাটার্ন ম্যাচিং দিকটি আরও স্পষ্টভাবে দেখতে, Listing 19-4 বিবেচনা করুন, যা একটি টাপল ডিস্ট্রাকচার করতে let-এর সাথে একটি প্যাটার্ন ব্যবহার করে।

fn main() {
    let (x, y, z) = (1, 2, 3);
}

এখানে, আমরা একটি টাপলকে একটি প্যাটার্নের সাথে মেলাই। Rust (1, 2, 3) মানটিকে (x, y, z) প্যাটার্নের সাথে তুলনা করে এবং দেখে যে মানটি প্যাটার্নের সাথে মেলে, তাই Rust 1-কে x-এর সাথে, 2-কে y-এর সাথে এবং 3-কে z-এর সাথে বাইন্ড করে। আপনি এই টাপল প্যাটার্নটিকে এর মধ্যে তিনটি পৃথক ভেরিয়েবল প্যাটার্ন নেস্ট করার মতো ভাবতে পারেন।

যদি প্যাটার্নের এলিমেন্টের সংখ্যা টাপলের এলিমেন্টের সংখ্যার সাথে না মেলে, তাহলে সামগ্রিক টাইপ মিলবে না এবং আমরা একটি কম্পাইলার error পাব। উদাহরণস্বরূপ, Listing 19-5 দুটি ভেরিয়েবলের মধ্যে তিনটি এলিমেন্ট সহ একটি টাপল ডিস্ট্রাকচার করার একটি প্রচেষ্টা দেখায়, যা কাজ করবে না।

fn main() {
    let (x, y) = (1, 2, 3);
}

এই কোডটি কম্পাইল করার চেষ্টা করলে এই টাইপ error পাওয়া যায়:

$ 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

Error টি ঠিক করার জন্য, আমরা _ বা .. ব্যবহার করে টাপলের এক বা একাধিক মান উপেক্ষা করতে পারি, যেমনটি আপনি “Ignoring Values in a Pattern” বিভাগে দেখতে পাবেন। যদি সমস্যাটি হয় যে প্যাটার্নে আমাদের অনেকগুলি ভেরিয়েবল রয়েছে, তাহলে সমাধান হল ভেরিয়েবলগুলি সরিয়ে টাইপগুলিকে মেলানো যাতে ভেরিয়েবলের সংখ্যা টাপলের এলিমেন্টের সংখ্যার সমান হয়।

ফাংশন প্যারামিটার (Function Parameters)

ফাংশন প্যারামিটারগুলিও প্যাটার্ন হতে পারে। Listing 19-6-এর কোড, যা foo নামে একটি ফাংশন ঘোষণা করে যা i32 টাইপের x নামে একটি প্যারামিটার নেয়, এখন আপনার কাছে পরিচিত হওয়া উচিত।

fn foo(x: i32) {
    // code goes here
}

fn main() {}

x অংশটি একটি প্যাটার্ন! যেমনটি আমরা let-এর সাথে করেছি, আমরা একটি ফাংশনের আর্গুমেন্টে একটি টাপলকে প্যাটার্নের সাথে মেলাতে পারি। Listing 19-7 একটি ফাংশনে পাস করার সময় একটি টাপলের মানগুলিকে বিভক্ত করে।

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 মান।

আমরা ক্লোজার প্যারামিটার তালিকাতেও একইভাবে প্যাটার্ন ব্যবহার করতে পারি যেমনটি ফাংশন প্যারামিটার তালিকায় করা হয়, কারণ ক্লোজারগুলি ফাংশনের মতোই, যেমনটি Chapter 13-এ আলোচনা করা হয়েছে।

এই সময়ে, আপনি প্যাটার্ন ব্যবহার করার বেশ কয়েকটি উপায় দেখেছেন, কিন্তু প্যাটার্নগুলি আমরা যেখানে ব্যবহার করতে পারি সেখানে সব জায়গায় একই কাজ করে না। কিছু জায়গায়, প্যাটার্নগুলিকে অবশ্যই ইরিফিউটেবল হতে হবে; অন্য পরিস্থিতিতে, সেগুলি রিফিউটেবল হতে পারে। আমরা পরবর্তীতে এই দুটি ধারণা নিয়ে আলোচনা করব।