প্যাটার্ন সিনট্যাক্স (Pattern Syntax)

এই বিভাগে, আমরা প্যাটার্নে ব্যবহৃত সমস্ত বৈধ সিনট্যাক্স সংগ্রহ করব এবং আলোচনা করব কেন এবং কখন আপনি তাদের প্রতিটি ব্যবহার করতে চাইতে পারেন।

লিটারেল ম্যাচিং (Matching Literals)

আপনি যেমন Chapter 6-এ দেখেছেন, আপনি সরাসরি লিটারেলের সাথে প্যাটার্ন মেলাতে পারেন। নিম্নলিখিত কোড কিছু উদাহরণ দেয়:

fn main() {
    let x = 1;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

এই কোডটি one প্রিন্ট করবে কারণ x-এর মান হল 1। এই সিনট্যাক্সটি দরকারী যখন আপনি চান যে আপনার কোডটি যদি কোনও নির্দিষ্ট কংক্রিট মান পায় তাহলে একটি অ্যাকশন নেবে।

নামযুক্ত ভেরিয়েবল ম্যাচিং (Matching Named Variables)

নামযুক্ত ভেরিয়েবলগুলি হল ইরিফিউটেবল প্যাটার্ন যা যেকোনো মানের সাথে মেলে এবং আমরা বইটিতে সেগুলি অনেকবার ব্যবহার করেছি। যাইহোক, যখন আপনি match, if let, বা while let এক্সপ্রেশনে নামযুক্ত ভেরিয়েবল ব্যবহার করেন তখন একটি জটিলতা দেখা দেয়। যেহেতু এই ধরনের প্রতিটি এক্সপ্রেশন একটি নতুন স্কোপ শুরু করে, তাই এক্সপ্রেশনের ভিতরে একটি প্যাটার্নের অংশ হিসাবে ঘোষিত ভেরিয়েবলগুলি বাইরের একই নামের ভেরিয়েবলগুলিকে শ্যাডো করবে, যেমনটি সমস্ত ভেরিয়েবলের ক্ষেত্রে হয়। Listing 19-11-এ, আমরা x নামে একটি ভেরিয়েবল ঘোষণা করি যার মান Some(5) এবং একটি ভেরিয়েবল y যার মান 10। তারপর আমরা x মানের উপর একটি match এক্সপ্রেশন তৈরি করি। ম্যাচ আর্মের প্যাটার্নগুলি এবং শেষে println! দেখুন এবং এই কোডটি চালানোর আগে বা আরও পড়ার আগে কোডটি কী প্রিন্ট করবে তা বোঝার চেষ্টা করুন।

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {y}"),
        _ => println!("Default case, x = {x:?}"),
    }

    println!("at the end: x = {x:?}, y = {y}");
}

আসুন match এক্সপ্রেশনটি চলার সময় কী ঘটে তা দেখি। প্রথম ম্যাচ আর্মের প্যাটার্নটি x-এর সংজ্ঞায়িত মানের সাথে মেলে না, তাই কোড চলতে থাকে।

দ্বিতীয় ম্যাচ আর্মের প্যাটার্নটি y নামে একটি নতুন ভেরিয়েবল প্রবর্তন করে যা একটি Some মানের ভিতরের যেকোনো মানের সাথে মিলবে। যেহেতু আমরা match এক্সপ্রেশনের ভিতরের একটি নতুন স্কোপে আছি, তাই এটি একটি নতুন y ভেরিয়েবল, শুরুতে ঘোষিত y নয় যার মান 10। এই নতুন y বাইন্ডিং একটি Some-এর ভিতরের যেকোনো মানের সাথে মিলবে, যা আমাদের x-এ রয়েছে। অতএব, এই নতুন y, x-এর Some-এর ভিতরের মানের সাথে বাইন্ড করে। সেই মানটি হল 5, তাই সেই আর্মের জন্য এক্সপ্রেশনটি এক্সিকিউট হয় এবং Matched, y = 5 প্রিন্ট করে।

যদি x Some(5)-এর পরিবর্তে একটি None মান হত, তাহলে প্রথম দুটি আর্মের প্যাটার্নগুলি মিলত না, তাই মানটি আন্ডারস্কোরের সাথে মিলত। আমরা আন্ডারস্কোর আর্মের প্যাটার্নে x ভেরিয়েবলটি প্রবর্তন করিনি, তাই এক্সপ্রেশনের x এখনও বাইরের x যা শ্যাডো করা হয়নি। এই অনুমানমূলক ক্ষেত্রে, match প্রিন্ট করত Default case, x = None

যখন match এক্সপ্রেশনটি শেষ হয়, তখন এর স্কোপ শেষ হয়, এবং সেইসাথে ভিতরের y-এর স্কোপও শেষ হয়। শেষ println! at the end: x = Some(5), y = 10 তৈরি করে।

একটি match এক্সপ্রেশন তৈরি করতে যা বাইরের x এবং y-এর মানগুলির তুলনা করে, বিদ্যমান y ভেরিয়েবলটিকে শ্যাডো করে এমন একটি নতুন ভেরিয়েবল প্রবর্তন করার পরিবর্তে, আমাদের পরিবর্তে একটি ম্যাচ গার্ড কন্ডিশনাল ব্যবহার করতে হবে। আমরা পরে “Extra Conditionals with Match Guards” বিভাগে ম্যাচ গার্ড সম্পর্কে কথা বলব।

একাধিক প্যাটার্ন (Multiple Patterns)

আপনি | সিনট্যাক্স ব্যবহার করে একাধিক প্যাটার্ন মেলাতে পারেন, যেটি হল প্যাটার্ন অথবা অপারেটর। উদাহরণস্বরূপ, নিম্নলিখিত কোডে আমরা x-এর মানকে ম্যাচ আর্মগুলির সাথে মেলাই, যার প্রথমটিতে একটি অথবা বিকল্প রয়েছে, যার অর্থ হল যদি x-এর মান সেই আর্মের যেকোনো মানের সাথে মেলে, তাহলে সেই আর্মের কোড চলবে:

fn main() {
    let x = 1;

    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

এই কোডটি one or two প্রিন্ট করবে।

..= দিয়ে মানের রেঞ্জ ম্যাচিং (Matching Ranges of Values with ..=)

..= সিনট্যাক্স আমাদের মানগুলির একটি অন্তর্ভুক্তিমূলক রেঞ্জের সাথে মেলাতে দেয়। নিম্নলিখিত কোডে, যখন একটি প্যাটার্ন প্রদত্ত রেঞ্জের যেকোনো মানের সাথে মেলে, তখন সেই আর্মটি এক্সিকিউট হবে:

fn main() {
    let x = 5;

    match x {
        1..=5 => println!("one through five"),
        _ => println!("something else"),
    }
}

যদি x 1, 2, 3, 4, বা 5 হয়, তাহলে প্রথম আর্মটি মিলবে। এই সিনট্যাক্সটি একই ধারণা প্রকাশ করার জন্য | অপারেটর ব্যবহার করার চেয়ে একাধিক ম্যাচ মানের জন্য আরও সুবিধাজনক; যদি আমরা | ব্যবহার করতাম তাহলে আমাদের 1 | 2 | 3 | 4 | 5 নির্দিষ্ট করতে হত। একটি রেঞ্জ নির্দিষ্ট করা অনেক ছোট, বিশেষ করে যদি আমরা, উদাহরণস্বরূপ, 1 থেকে 1,000-এর মধ্যে যেকোনো সংখ্যার সাথে মেলাতে চাই!

কম্পাইলার কম্পাইল করার সময় পরীক্ষা করে যে রেঞ্জটি খালি নয় এবং যেহেতু Rust শুধুমাত্র char এবং সাংখ্যিক মানের জন্য বলতে পারে যে একটি রেঞ্জ খালি কিনা, তাই রেঞ্জগুলি শুধুমাত্র সাংখ্যিক বা char মানের সাথে অনুমোদিত।

এখানে char মানের রেঞ্জ ব্যবহার করার একটি উদাহরণ দেওয়া হল:

fn main() {
    let x = 'c';

    match x {
        'a'..='j' => println!("early ASCII letter"),
        'k'..='z' => println!("late ASCII letter"),
        _ => println!("something else"),
    }
}

Rust বলতে পারে যে 'c' প্রথম প্যাটার্নের রেঞ্জের মধ্যে রয়েছে এবং early ASCII letter প্রিন্ট করে।

মানগুলিকে ভেঙে আলাদা করতে ডিস্ট্রাকচারিং (Destructuring to Break Apart Values)

আমরা স্ট্রাক্ট, এনাম এবং টাপলগুলিকে ডিস্ট্রাকচার করতে প্যাটার্ন ব্যবহার করতে পারি যাতে এই মানগুলির বিভিন্ন অংশ ব্যবহার করা যায়। আসুন প্রতিটি মানের মধ্য দিয়ে যাই।

ডিস্ট্রাকচারিং স্ট্রাক্ট (Destructuring Structs)

Listing 19-12 দুটি ফিল্ড, x এবং y সহ একটি Point স্ট্রাক্ট দেখায়, যাকে আমরা একটি let স্টেটমেন্ট সহ একটি প্যাটার্ন ব্যবহার করে আলাদা করতে পারি।

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}

এই কোডটি a এবং b ভেরিয়েবল তৈরি করে যা p স্ট্রাক্টের x এবং y ফিল্ডের মানের সাথে মেলে। এই উদাহরণটি দেখায় যে প্যাটার্নের ভেরিয়েবলের নামগুলি স্ট্রাক্টের ফিল্ডের নামের সাথে মিলতে হবে না। যাইহোক, কোন ভেরিয়েবলগুলি কোন ফিল্ড থেকে এসেছে তা মনে রাখা সহজ করার জন্য ভেরিয়েবলের নামগুলিকে ফিল্ডের নামের সাথে মেলানো সাধারণ। এই সাধারণ ব্যবহারের কারণে এবং let Point { x: x, y: y } = p; লেখায় অনেক ডুপ্লিকেশন থাকার কারণে, Rust-এর স্ট্রাক্ট ফিল্ডগুলির সাথে মেলে এমন প্যাটার্নগুলির জন্য একটি সংক্ষিপ্ত রূপ রয়েছে: আপনাকে শুধুমাত্র স্ট্রাক্ট ফিল্ডের নাম তালিকাভুক্ত করতে হবে এবং প্যাটার্ন থেকে তৈরি হওয়া ভেরিয়েবলগুলির একই নাম থাকবে। Listing 19-13, Listing 19-12-এর কোডের মতোই আচরণ করে, কিন্তু let প্যাটার্নে তৈরি হওয়া ভেরিয়েবলগুলি a এবং b-এর পরিবর্তে x এবং y

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

এই কোডটি x এবং y ভেরিয়েবল তৈরি করে যা p ভেরিয়েবলের x এবং y ফিল্ডের সাথে মেলে। ফলাফল হল যে x এবং y ভেরিয়েবলগুলিতে p স্ট্রাক্টের মান রয়েছে।

আমরা সমস্ত ফিল্ডের জন্য ভেরিয়েবল তৈরি করার পরিবর্তে স্ট্রাক্ট প্যাটার্নের অংশ হিসাবে আক্ষরিক মানগুলির সাথেও ডিস্ট্রাকচার করতে পারি। এটি করার ফলে আমাদের অন্য ফিল্ডগুলিকে ডিস্ট্রাকচার করার জন্য ভেরিয়েবল তৈরি করার সময় নির্দিষ্ট মানগুলির জন্য কিছু ফিল্ড পরীক্ষা করার অনুমতি দেয়।

Listing 19-14-এ, আমাদের একটি match এক্সপ্রেশন রয়েছে যা Point মানগুলিকে তিনটি ক্ষেত্রে পৃথক করে: যে পয়েন্টগুলি সরাসরি x অক্ষের উপর থাকে (y = 0 হলে এটি সত্য), y অক্ষের উপর (x = 0), বা কোনওটিতেই নয়।

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {x}"),
        Point { x: 0, y } => println!("On the y axis at {y}"),
        Point { x, y } => {
            println!("On neither axis: ({x}, {y})");
        }
    }
}

প্রথম আর্মটি x অক্ষের উপর অবস্থিত যেকোনো পয়েন্টের সাথে মিলবে, এটি নির্দিষ্ট করে যে y ফিল্ডটি মিলবে যদি এর মান আক্ষরিক 0-এর সাথে মেলে। প্যাটার্নটি এখনও একটি x ভেরিয়েবল তৈরি করে যা আমরা এই আর্মের কোডের জন্য ব্যবহার করতে পারি।

একইভাবে, দ্বিতীয় আর্মটি y অক্ষের যেকোনো পয়েন্টের সাথে মেলে, এটি নির্দিষ্ট করে যে x ফিল্ডটি মিলবে যদি এর মান 0 হয় এবং y ফিল্ডের মানের জন্য একটি ভেরিয়েবল y তৈরি করে। তৃতীয় আর্মটি কোনও আক্ষরিক নির্দিষ্ট করে না, তাই এটি অন্য যেকোনো Point-এর সাথে মেলে এবং x এবং y উভয় ফিল্ডের জন্য ভেরিয়েবল তৈরি করে।

এই উদাহরণে, x-এ একটি 0 থাকার কারণে p মানটি দ্বিতীয় আর্মের সাথে মেলে, তাই এই কোডটি On the y axis at 7 প্রিন্ট করবে।

মনে রাখবেন যে একটি match এক্সপ্রেশন প্রথম ম্যাচিং প্যাটার্নটি খুঁজে পাওয়ার পরে আর্মগুলি পরীক্ষা করা বন্ধ করে দেয়, তাই যদিও Point { x: 0, y: 0} x অক্ষ এবং y অক্ষের উপর রয়েছে, তবুও এই কোডটি শুধুমাত্র On the x axis at 0 প্রিন্ট করবে।

ডিস্ট্রাকচারিং এনাম (Destructuring Enums)

আমরা এই বইটিতে এনামগুলিকে ডিস্ট্রাকচার করেছি (উদাহরণস্বরূপ, Chapter 6-এর Listing 6-5), কিন্তু এখনও স্পষ্টভাবে আলোচনা করিনি যে একটি এনামকে ডিস্ট্রাকচার করার প্যাটার্নটি এনামের মধ্যে সংরক্ষিত ডেটা যেভাবে সংজ্ঞায়িত করা হয়েছে তার সাথে সঙ্গতিপূর্ণ। একটি উদাহরণ হিসাবে, Listing 19-15-এ আমরা Listing 6-2 থেকে Message এনাম ব্যবহার করি এবং প্যাটার্নগুলির সাথে একটি match লিখি যা প্রতিটি অভ্যন্তরীণ মানকে ডিস্ট্রাকচার করবে।

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.");
        }
        Message::Move { x, y } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change the color to red {r}, green {g}, and blue {b}");
        }
    }
}

এই কোডটি Change the color to red 0, green 160, and blue 255 প্রিন্ট করবে। অন্যান্য আর্ম থেকে কোড চালানোর জন্য msg-এর মান পরিবর্তন করার চেষ্টা করুন।

কোনও ডেটা ছাড়া এনাম ভেরিয়েন্টগুলির জন্য, যেমন Message::Quit, আমরা মানটিকে আরও ডিস্ট্রাকচার করতে পারি না। আমরা শুধুমাত্র আক্ষরিক Message::Quit মানের উপর মেলাতে পারি এবং সেই প্যাটার্নে কোনও ভেরিয়েবল নেই।

স্ট্রাক্ট-জাতীয় এনাম ভেরিয়েন্টগুলির জন্য, যেমন Message::Move, আমরা স্ট্রাক্টগুলির সাথে মেলানোর জন্য নির্দিষ্ট করা প্যাটার্নের অনুরূপ একটি প্যাটার্ন ব্যবহার করতে পারি। ভেরিয়েন্টের নামের পরে, আমরা কোঁকড়া বন্ধনী রাখি এবং তারপর ভেরিয়েবল সহ ফিল্ডগুলি তালিকাভুক্ত করি যাতে আমরা এই আর্মের কোডে ব্যবহার করার জন্য টুকরোগুলি ভেঙে ফেলতে পারি। এখানে আমরা Listing 19-13-এ যেমন করেছি তেমনই সংক্ষিপ্ত রূপ ব্যবহার করি।

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

নেস্টেড স্ট্রাক্ট এবং এনাম ডিস্ট্রাকচার করা (Destructuring Nested Structs and Enums)

এখন পর্যন্ত, আমাদের সমস্ত উদাহরণ এক লেভেল গভীরতার স্ট্রাক্ট বা এনামগুলির সাথে মিলছিল, কিন্তু ম্যাচিং নেস্টেড আইটেমগুলিতেও কাজ করতে পারে! উদাহরণস্বরূপ, আমরা Listing 19-16-এ দেখানো ChangeColor মেসেজে RGB এবং HSV রং সমর্থন করার জন্য Listing 19-15-এর কোডটি রিফ্যাক্টর করতে পারি।

enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("Change color to red {r}, green {g}, and blue {b}");
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color to hue {h}, saturation {s}, value {v}");
        }
        _ => (),
    }
}

match এক্সপ্রেশনের প্রথম আর্মের প্যাটার্নটি একটি Message::ChangeColor এনাম ভেরিয়েন্টের সাথে মেলে যাতে একটি Color::Rgb ভেরিয়েন্ট থাকে; তারপর প্যাটার্নটি তিনটি অভ্যন্তরীণ i32 মানের সাথে বাইন্ড করে। দ্বিতীয় আর্মের প্যাটার্নটিও একটি Message::ChangeColor এনাম ভেরিয়েন্টের সাথে মেলে, কিন্তু অভ্যন্তরীণ এনামটি পরিবর্তে Color::Hsv-এর সাথে মেলে। আমরা এই জটিল শর্তগুলি একটি match এক্সপ্রেশনে নির্দিষ্ট করতে পারি, এমনকি যদি দুটি এনাম জড়িত থাকে।

স্ট্রাক্ট এবং টাপল ডিস্ট্রাকচার করা (Destructuring Structs and Tuples)

আমরা আরও জটিল উপায়ে ডিস্ট্রাকচারিং প্যাটার্নগুলিকে মিশ্রিত করতে, মেলাতে এবং নেস্ট করতে পারি। নিম্নলিখিত উদাহরণটি একটি জটিল ডিস্ট্রাকচার দেখায় যেখানে আমরা একটি টাপলের ভিতরে স্ট্রাক্ট এবং টাপলগুলিকে নেস্ট করি এবং সমস্ত প্রিমিটিভ মানগুলিকে ডিস্ট্রাকচার করি:

fn main() {
    struct Point {
        x: i32,
        y: i32,
    }

    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}

এই কোডটি আমাদের জটিল টাইপগুলিকে তাদের উপাদান অংশে ভেঙে দিতে দেয় যাতে আমরা যে মানগুলিতে আগ্রহী সেগুলি আলাদাভাবে ব্যবহার করতে পারি।

প্যাটার্নগুলির সাথে ডিস্ট্রাকচারিং হল মানগুলির টুকরোগুলি ব্যবহার করার একটি সুবিধাজনক উপায়, যেমন একটি স্ট্রাক্টের প্রতিটি ফিল্ডের মান, একে অপরের থেকে আলাদাভাবে।

একটি প্যাটার্নের মান উপেক্ষা করা (Ignoring Values in a Pattern)

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

_ দিয়ে একটি সম্পূর্ণ মান উপেক্ষা করা (Ignoring an Entire Value with _)

আমরা একটি ওয়াইল্ডকার্ড প্যাটার্ন হিসাবে আন্ডারস্কোর ব্যবহার করেছি যা যেকোনো মানের সাথে মিলবে কিন্তু মানের সাথে বাইন্ড করবে না। এটি একটি match এক্সপ্রেশনের শেষ আর্ম হিসাবে বিশেষভাবে দরকারী, তবে আমরা এটিকে ফাংশন প্যারামিটার সহ যেকোনো প্যাটার্নে ব্যবহার করতে পারি, যেমনটি Listing 19-17-এ দেখানো হয়েছে।

fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {y}");
}

fn main() {
    foo(3, 4);
}

এই কোডটি প্রথম আর্গুমেন্ট হিসাবে পাস করা মান 3-কে সম্পূর্ণরূপে উপেক্ষা করবে এবং This code only uses the y parameter: 4 প্রিন্ট করবে।

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

একটি মানের অংশবিশেষ উপেক্ষা করতে নেস্টেড _ ব্যবহার করা (Ignoring Parts of a Value with a Nested _)

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

fn main() {
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);

    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }

    println!("setting is {setting_value:?}");
}

এই কোডটি Can't overwrite an existing customized value প্রিন্ট করবে এবং তারপর setting is Some(5) প্রিন্ট করবে। প্রথম ম্যাচ আর্মে, আমাদের Some ভেরিয়েন্টের ভিতরের মানগুলির সাথে মেলানো বা ব্যবহার করার প্রয়োজন নেই, তবে আমাদের setting_value এবং new_setting_value যখন Some ভেরিয়েন্ট হয় তখন সেই ক্ষেত্রটির জন্য পরীক্ষা করতে হবে। সেই ক্ষেত্রে, আমরা setting_value পরিবর্তন না করার কারণ প্রিন্ট করি এবং এটি পরিবর্তন হয় না।

অন্যান্য সমস্ত ক্ষেত্রে (যদি setting_value বা new_setting_value None হয়) দ্বিতীয় আর্মের _ প্যাটার্ন দ্বারা প্রকাশিত, আমরা new_setting_value-কে setting_value হতে দিতে চাই।

আমরা একটি প্যাটার্নের মধ্যে একাধিক স্থানে আন্ডারস্কোর ব্যবহার করে নির্দিষ্ট মান উপেক্ষা করতে পারি। Listing 19-19 পাঁচটি আইটেমের একটি টাপলের দ্বিতীয় এবং চতুর্থ মান উপেক্ষা করার একটি উদাহরণ দেখায়।

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {first}, {third}, {fifth}")
        }
    }
}

এই কোডটি Some numbers: 2, 8, 32 প্রিন্ট করবে এবং 4 এবং 16 মানগুলি উপেক্ষা করা হবে।

একটি অব্যবহৃত ভেরিয়েবল উপেক্ষা করতে এর নামের শুরুতে _ ব্যবহার করা (Ignoring an Unused Variable by Starting Its Name with _)

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

fn main() {
    let _x = 5;
    let y = 10;
}

এখানে আমরা y ভেরিয়েবলটি ব্যবহার না করার বিষয়ে একটি সতর্কতা পাই, কিন্তু _x ব্যবহার না করার বিষয়ে আমরা কোনও সতর্কতা পাই না।

লক্ষ্য করুন যে শুধুমাত্র _ ব্যবহার করা এবং একটি আন্ডারস্কোর দিয়ে শুরু হওয়া একটি নাম ব্যবহারের মধ্যে একটি সূক্ষ্ম পার্থক্য রয়েছে। সিনট্যাক্স _x এখনও মানটিকে ভেরিয়েবলের সাথে বাইন্ড করে, যেখানে _ মোটেও বাইন্ড করে না। এই পার্থক্যটি গুরুত্বপূর্ণ এমন একটি ক্ষেত্র দেখানোর জন্য, Listing 19-21 আমাদের একটি error দেবে।

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_s) = s {
        println!("found a string");
    }

    println!("{s:?}");
}

আমরা একটি error পাব কারণ s-এর মান এখনও _s-এ সরানো হবে, যা আমাদের আবার s ব্যবহার করতে বাধা দেয়। যাইহোক, নিজে থেকে আন্ডারস্কোর ব্যবহার করা কখনই মানের সাথে বাইন্ড করে না। Listing 19-22 কোনও error ছাড়াই কম্পাইল হবে কারণ s, _-তে সরানো হয় না।

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_) = s {
        println!("found a string");
    }

    println!("{s:?}");
}

এই কোডটি ঠিকঠাক কাজ করে কারণ আমরা কখনই s-কে কোনও কিছুর সাথে বাইন্ড করি না; এটি সরানো হয় না।

.. দিয়ে একটি মানের অবশিষ্ট অংশগুলি উপেক্ষা করা (Ignoring Remaining Parts of a Value with ..)

অনেকগুলি অংশ সহ মানগুলির সাথে, আমরা নির্দিষ্ট অংশগুলি ব্যবহার করতে এবং বাকিগুলি উপেক্ষা করতে .. সিনট্যাক্স ব্যবহার করতে পারি, প্রতিটি উপেক্ষিত মানের জন্য আন্ডারস্কোর তালিকাভুক্ত করার প্রয়োজন এড়াতে। .. প্যাটার্নটি একটি মানের যে কোনও অংশকে উপেক্ষা করে যা আমরা প্যাটার্নের বাকি অংশে স্পষ্টভাবে মেলিনি। Listing 19-23-এ, আমাদের একটি Point স্ট্রাক্ট রয়েছে যা ত্রিমাত্রিক স্থানে একটি স্থানাঙ্ক ধারণ করে। match এক্সপ্রেশনে, আমরা শুধুমাত্র x স্থানাঙ্কের উপর কাজ করতে চাই এবং y এবং z ফিল্ডের মানগুলি উপেক্ষা করতে চাই।

fn main() {
    struct Point {
        x: i32,
        y: i32,
        z: i32,
    }

    let origin = Point { x: 0, y: 0, z: 0 };

    match origin {
        Point { x, .. } => println!("x is {x}"),
    }
}

আমরা x মান তালিকাভুক্ত করি এবং তারপর শুধু .. প্যাটার্ন অন্তর্ভুক্ত করি। এটি y: _ এবং z: _ তালিকাভুক্ত করার চেয়ে দ্রুততর, বিশেষ করে যখন আমরা এমন স্ট্রাক্টগুলির সাথে কাজ করছি যেখানে প্রচুর ফিল্ড রয়েছে এবং শুধুমাত্র একটি বা দুটি ফিল্ড প্রাসঙ্গিক।

.. সিনট্যাক্সটি যতগুলি মানের প্রয়োজন ততগুলিতে প্রসারিত হবে। Listing 19-24 একটি টাপলের সাথে .. ব্যবহার করার উপায় দেখায়।

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}

এই কোডে, প্রথম এবং শেষ মান first এবং last দিয়ে মেলানো হয়। .. মাঝের সবকিছু মেলবে এবং উপেক্ষা করবে।

যাইহোক, .. ব্যবহার করা অবশ্যই দ্ব্যর্থহীন হতে হবে। যদি এটি অস্পষ্ট হয় যে কোন মানগুলি মেলানোর জন্য উদ্দিষ্ট এবং কোনটি উপেক্ষা করা উচিত, Rust আমাদের একটি error দেবে। Listing 19-25 দ্ব্যর্থহীনভাবে .. ব্যবহারের একটি উদাহরণ দেখায়, তাই এটি কম্পাইল হবে না।

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {second}")
        },
    }
}

আমরা যখন এই উদাহরণটি কম্পাইল করি, তখন আমরা এই error টি পাই:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
 --> src/main.rs:5:22
  |
5 |         (.., second, ..) => {
  |          --          ^^ can only be used once per tuple pattern
  |          |
  |          previously used here

error: could not compile `patterns` (bin "patterns") due to 1 previous error

Rust-এর পক্ষে নির্ধারণ করা অসম্ভব যে second-এর সাথে একটি মান মেলানোর আগে টাপলের কতগুলি মান উপেক্ষা করতে হবে এবং তারপর আরও কতগুলি মান উপেক্ষা করতে হবে। এই কোডটির অর্থ হতে পারে যে আমরা 2 উপেক্ষা করতে চাই, second-কে 4-এর সাথে বাইন্ড করতে চাই এবং তারপর 8, 16 এবং 32 উপেক্ষা করতে চাই; অথবা আমরা 2 এবং 4 উপেক্ষা করতে চাই, second-কে 8-এর সাথে বাইন্ড করতে চাই এবং তারপর 16 এবং 32 উপেক্ষা করতে চাই; এবং আরও অনেক কিছু। second ভেরিয়েবলের নামটি Rust-এর কাছে বিশেষ কিছু বোঝায় না, তাই আমরা একটি কম্পাইলার error পাই কারণ এইভাবে দুটি জায়গায় .. ব্যবহার করা দ্ব্যর্থহীন।

ম্যাচ গার্ড সহ অতিরিক্ত শর্ত (Extra Conditionals with Match Guards)

একটি ম্যাচ গার্ড হল একটি অতিরিক্ত if শর্ত, যা একটি match আর্মের প্যাটার্নের পরে নির্দিষ্ট করা হয়, যেটি সেই আর্মটি বেছে নেওয়ার জন্য অবশ্যই মিলতে হবে। ম্যাচ গার্ডগুলি এমন আরও জটিল ধারণা প্রকাশ করার জন্য দরকারী যা একটি প্যাটার্ন একা অনুমতি দেয়। এগুলি শুধুমাত্র match এক্সপ্রেশনেই পাওয়া যায়, if let বা while let এক্সপ্রেশনে নয়।

শর্তটি প্যাটার্নে তৈরি করা ভেরিয়েবল ব্যবহার করতে পারে। Listing 19-26 একটি match দেখায় যেখানে প্রথম আর্মের প্যাটার্ন Some(x) এবং একটি ম্যাচ গার্ড if x % 2 == 0 রয়েছে (যা সংখ্যাটি জোড় হলে সত্য হবে)।

fn main() {
    let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
        Some(x) => println!("The number {x} is odd"),
        None => (),
    }
}

এই উদাহরণটি The number 4 is even প্রিন্ট করবে। যখন num-কে প্রথম আর্মের প্যাটার্নের সাথে তুলনা করা হয়, তখন এটি মেলে, কারণ Some(4) Some(x)-এর সাথে মেলে। তারপর ম্যাচ গার্ড পরীক্ষা করে যে x-কে 2 দ্বারা ভাগ করার অবশিষ্টাংশ 0-এর সমান কিনা এবং যেহেতু এটি, তাই প্রথম আর্মটি নির্বাচন করা হয়।

যদি num Some(5) হত, তাহলে প্রথম আর্মের ম্যাচ গার্ডটি মিথ্যা হত কারণ 5 কে 2 দ্বারা ভাগ করার অবশিষ্টাংশ হল 1, যা 0-এর সমান নয়। Rust তারপর দ্বিতীয় আর্মে যেত, যেটি মিলত কারণ দ্বিতীয় আর্মের কোনও ম্যাচ গার্ড নেই এবং তাই এটি যেকোনো Some ভেরিয়েন্টের সাথে মেলে।

if x % 2 == 0 শর্তটি একটি প্যাটার্নের মধ্যে প্রকাশ করার কোনও উপায় নেই, তাই ম্যাচ গার্ড আমাদের এই লজিকটি প্রকাশ করার ক্ষমতা দেয়। এই অতিরিক্ত অভিব্যক্তির নেতিবাচক দিক হল যে কম্পাইলার ম্যাচ গার্ড এক্সপ্রেশন জড়িত থাকলে এক্সহসটিভনেস পরীক্ষা করার চেষ্টা করে না।

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

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {n}"),
        _ => println!("Default case, x = {x:?}"),
    }

    println!("at the end: x = {x:?}, y = {y}");
}

এই কোডটি এখন Default case, x = Some(5) প্রিন্ট করবে। দ্বিতীয় ম্যাচ আর্মের প্যাটার্নটি একটি নতুন ভেরিয়েবল y প্রবর্তন করে না যা বাইরের y-কে শ্যাডো করবে, মানে আমরা ম্যাচ গার্ডে বাইরের y ব্যবহার করতে পারি। Some(y) হিসাবে প্যাটার্নটি নির্দিষ্ট করার পরিবর্তে, যা বাইরের y-কে শ্যাডো করত, আমরা Some(n) নির্দিষ্ট করি। এটি একটি নতুন ভেরিয়েবল n তৈরি করে যা কোনও কিছুকে শ্যাডো করে না কারণ match-এর বাইরে কোনও n ভেরিয়েবল নেই।

ম্যাচ গার্ড if n == y একটি প্যাটার্ন নয় এবং তাই নতুন ভেরিয়েবল প্রবর্তন করে না। এই y হল বাইরের y, একটি নতুন শ্যাডো করা y নয় এবং আমরা n-কে y-এর সাথে তুলনা করে বাইরের y-এর মতো একই মান আছে এমন একটি মান খুঁজতে পারি।

আপনি একটি ম্যাচ গার্ডে অথবা অপারেটর | ব্যবহার করে একাধিক প্যাটার্ন নির্দিষ্ট করতে পারেন; ম্যাচ গার্ড শর্তটি সমস্ত প্যাটার্নের ক্ষেত্রে প্রযোজ্য হবে। Listing 19-28 | ব্যবহার করে একটি প্যাটার্নকে একটি ম্যাচ গার্ডের সাথে একত্রিত করার সময় অগ্রাধিকার দেখায়। এই উদাহরণের গুরুত্বপূর্ণ অংশ হল if y ম্যাচ গার্ডটি 4, 5, এবং 6-এর ক্ষেত্রে প্রযোজ্য, যদিও এটি দেখতে এমন হতে পারে যে if y শুধুমাত্র 6-এর ক্ষেত্রে প্রযোজ্য।

fn main() {
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}

ম্যাচ শর্তটি বলে যে আর্মটি শুধুমাত্র তখনই মেলে যদি x-এর মান 4, 5, বা 6-এর সমান হয় এবং যদি y true হয়। যখন এই কোডটি চালানো হয়, তখন প্রথম আর্মের প্যাটার্নটি মেলে কারণ x হল 4, কিন্তু ম্যাচ গার্ড if y মিথ্যা, তাই প্রথম আর্মটি বেছে নেওয়া হয় না। কোডটি দ্বিতীয় আর্মে চলে যায়, যেটি মেলে এবং এই প্রোগ্রামটি no প্রিন্ট করে। এর কারণ হল if শর্তটি শুধুমাত্র শেষ মান 6-এর ক্ষেত্রে নয়, বরং সম্পূর্ণ প্যাটার্ন 4 | 5 | 6-এর ক্ষেত্রে প্রযোজ্য। অন্য কথায়, একটি প্যাটার্নের সাথে একটি ম্যাচ গার্ডের অগ্রাধিকার এইভাবে আচরণ করে:

(4 | 5 | 6) if y => ...

এইটার পরিবর্তে:

4 | 5 | (6 if y) => ...

কোডটি চালানোর পরে, অগ্রাধিকারের আচরণটি স্পষ্ট হয়: যদি ম্যাচ গার্ডটি শুধুমাত্র | অপারেটর ব্যবহার করে নির্দিষ্ট করা মানগুলির তালিকার চূড়ান্ত মানের ক্ষেত্রে প্রয়োগ করা হত, তাহলে আর্মটি মিলত এবং প্রোগ্রামটি yes প্রিন্ট করত।

@ বাইন্ডিং (@ Bindings)

অ্যাট অপারেটর @ আমাদের একটি ভেরিয়েবল তৈরি করতে দেয় যা একটি মান ধারণ করে একই সাথে আমরা সেই মানটিকে একটি প্যাটার্ন ম্যাচের জন্য পরীক্ষা করছি। Listing 19-29-এ, আমরা পরীক্ষা করতে চাই যে একটি Message::Hello id ফিল্ড 3..=7 রেঞ্জের মধ্যে আছে কিনা। আমরা ভেরিয়েবল id_variable-এর সাথে মানটিও বাইন্ড করতে চাই যাতে আমরা আর্মের সাথে সম্পর্কিত কোডে এটি ব্যবহার করতে পারি। আমরা এই ভেরিয়েবলটির নাম id দিতে পারি, ফিল্ডের মতোই, কিন্তু এই উদাহরণের জন্য আমরা একটি ভিন্ন নাম ব্যবহার করব।

fn main() {
    enum Message {
        Hello { id: i32 },
    }

    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => println!("Found an id in range: {id_variable}"),
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {id}"),
    }
}

এই উদাহরণটি Found an id in range: 5 প্রিন্ট করবে। রেঞ্জ 3..=7-এর আগে id_variable @ নির্দিষ্ট করে, আমরা রেঞ্জের সাথে মিলে যাওয়া যেকোনো মান ক্যাপচার করছি এবং একই সাথে পরীক্ষা করছি যে মানটি রেঞ্জ প্যাটার্নের সাথে মেলে কিনা।

দ্বিতীয় আর্মে, যেখানে আমাদের প্যাটার্নে শুধুমাত্র একটি রেঞ্জ নির্দিষ্ট করা আছে, আর্মের সাথে সম্পর্কিত কোডে এমন একটি ভেরিয়েবল নেই যাতে id ফিল্ডের প্রকৃত মান রয়েছে। id ফিল্ডের মান 10, 11, বা 12 হতে পারত, কিন্তু সেই প্যাটার্নের সাথে থাকা কোডটি জানে না কোনটি। প্যাটার্ন কোডটি id ফিল্ড থেকে মান ব্যবহার করতে সক্ষম নয়, কারণ আমরা একটি ভেরিয়েবলে id মান সংরক্ষণ করিনি।

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

@ ব্যবহার করা আমাদের একটি মানের পরীক্ষা করতে এবং এটিকে একটি প্যাটার্নের মধ্যে একটি ভেরিয়েবলে সংরক্ষণ করতে দেয়।

সারসংক্ষেপ (Summary)

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

এরপর, বইটির শেষ চ্যাপ্টারের আগে, আমরা Rust-এর বিভিন্ন বৈশিষ্ট্যের কিছু উন্নত দিক দেখব।