প্যাটার্ন সিনট্যাক্স (Pattern Syntax)
এই সেকশনে, আমরা প্যাটার্নে বৈধ সমস্ত সিনট্যাক্স একত্রিত করব এবং আলোচনা করব কেন এবং কখন আপনি প্রতিটি ব্যবহার করতে চাইতে পারেন।
লিটারেলের সাথে ম্যাচিং (Matching Literals)
যেমনটি আপনি ৬ষ্ঠ অধ্যায়ে দেখেছেন, আপনি সরাসরি লিটারেলের (literals) সাথে প্যাটার্ন ম্যাচ করতে পারেন। নিচের কোডটি কিছু উদাহরণ দেয়:
fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), } }
এই কোডটি one প্রিন্ট করবে কারণ x এর মান 1। এই সিনট্যাক্সটি তখন কার্যকর যখন আপনি চান আপনার কোড একটি নির্দিষ্ট সুনির্দিষ্ট (concrete) মান পেলে কোনো একটি কাজ করুক।
নামযুক্ত ভেরিয়েবলের সাথে ম্যাচিং (Matching Named Variables)
নামযুক্ত ভেরিয়েবলগুলো হলো অখণ্ডনযোগ্য (irrefutable) প্যাটার্ন যা যেকোনো মানের সাথে মেলে, এবং আমরা এই বইয়ে অনেকবার এগুলো ব্যবহার করেছি। তবে, আপনি যখন match, if let, বা while let এক্সপ্রেশনে নামযুক্ত ভেরিয়েবল ব্যবহার করেন তখন একটি জটিলতা দেখা দেয়। যেহেতু এই ধরনের প্রতিটি এক্সপ্রেশন একটি নতুন স্কোপ (scope) শুরু করে, তাই এই এক্সপ্রেশনগুলোর ভিতরে একটি প্যাটার্নের অংশ হিসাবে ঘোষিত ভেরিয়েবলগুলো কনস্ট্রাক্টের বাইরের একই নামের ভেরিয়েবলগুলোকে শ্যাডো (shadow) করবে, যেমনটি সমস্ত ভেরিয়েবলের ক্ষেত্রে হয়। লিস্টিং ১৯-১১-এ, আমরা x নামে একটি ভেরিয়েবল ঘোষণা করি যার মান Some(5) এবং y নামে একটি ভেরিয়েবল যার মান 10। তারপর আমরা x মানের উপর একটি match এক্সপ্রেশন তৈরি করি। ম্যাচ arm-গুলোর প্যাটার্ন এবং শেষের 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 এক্সপ্রেশনটি চলার সময় কী ঘটে। প্রথম ম্যাচ arm-এর প্যাটার্নটি x-এর সংজ্ঞায়িত মানের সাথে মেলে না, তাই কোডটি চলতে থাকে।
দ্বিতীয় ম্যাচ arm-এর প্যাটার্নটি y নামে একটি নতুন ভেরিয়েবল প্রবর্তন করে যা একটি Some মানের ভিতরের যেকোনো মানের সাথে মিলবে। যেহেতু আমরা match এক্সপ্রেশনের ভিতরে একটি নতুন স্কোপে আছি, এটি একটি নতুন y ভেরিয়েবল, শুরুতে 10 মান দিয়ে ঘোষণা করা y নয়। এই নতুন y বাইন্ডিং একটি Some-এর ভিতরের যেকোনো মানের সাথে মিলবে, যা আমাদের x-এ আছে। সুতরাং, এই নতুন y, x-এর Some-এর ভিতরের মানের সাথে বাইন্ড হয়। সেই মানটি হলো 5, তাই সেই arm-এর এক্সপ্রেশনটি কার্যকর হয় এবং Matched, y = 5 প্রিন্ট করে।
যদি x Some(5)-এর পরিবর্তে একটি None মান হতো, তবে প্রথম দুটি arm-এর প্যাটার্ন মিলত না, তাই মানটি আন্ডারস্কোরের সাথে মিলত। আমরা আন্ডারস্কোর arm-এর প্যাটার্নে x ভেরিয়েবল প্রবর্তন করিনি, তাই এক্সপ্রেশনের x এখনও বাইরের x যা শ্যাডো হয়নি। এই কাল্পনিক ক্ষেত্রে, match প্রিন্ট করত Default case, x = None।
match এক্সপ্রেশন শেষ হয়ে গেলে, এর স্কোপও শেষ হয়ে যায়, এবং ভেতরের y-এর স্কোপও শেষ হয়ে যায়। শেষ println! at the end: x = Some(5), y = 10 তৈরি করে।
একটি match এক্সপ্রেশন তৈরি করতে যা বাইরের x এবং y-এর মান তুলনা করে, বিদ্যমান y ভেরিয়েবলকে শ্যাডো করে এমন একটি নতুন ভেরিয়েবল প্রবর্তন করার পরিবর্তে, আমাদের পরিবর্তে একটি ম্যাচ গার্ড কন্ডিশনাল (match guard conditional) ব্যবহার করতে হবে। আমরা ম্যাচ গার্ড সম্পর্কে পরে "ম্যাচ গার্ডের সাথে অতিরিক্ত কন্ডিশনাল" অংশে আলোচনা করব।
একাধিক প্যাটার্ন (Multiple Patterns)
match এক্সপ্রেশনে, আপনি | সিনট্যাক্স ব্যবহার করে একাধিক প্যাটার্ন ম্যাচ করতে পারেন, যা প্যাটার্নের or অপারেটর। উদাহরণস্বরূপ, নিম্নলিখিত কোডে আমরা x-এর মানকে ম্যাচ arm-গুলোর সাথে মেলাই, যার প্রথমটিতে একটি or বিকল্প রয়েছে, যার মানে যদি x-এর মান সেই arm-এর যেকোনো একটি মানের সাথে মেলে, তবে সেই arm-এর কোডটি চলবে:
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 ..=)
..= সিনট্যাক্স আমাদের একটি অন্তর্ভুক্তিমূলক (inclusive) মানের রেঞ্জের সাথে ম্যাচ করতে দেয়। নিম্নলিখিত কোডে, যখন একটি প্যাটার্ন প্রদত্ত রেঞ্জের মধ্যে যেকোনো মানের সাথে মেলে, তখন সেই arm-টি কার্যকর হবে:
fn main() { let x = 5; match x { 1..=5 => println!("one through five"), _ => println!("something else"), } }
যদি x 1, 2, 3, 4, বা 5 হয়, তবে প্রথম arm-টি মিলবে। এই সিনট্যাক্সটি একাধিক ম্যাচ মানের জন্য | অপারেটর ব্যবহার করে একই ধারণা প্রকাশ করার চেয়ে বেশি সুবিধাজনক; যদি আমরা | ব্যবহার করতাম, তবে আমাদের 1 | 2 | 3 | 4 | 5 নির্দিষ্ট করতে হতো। একটি রেঞ্জ নির্দিষ্ট করা অনেক ছোট, বিশেষ করে যদি আমরা, ধরা যাক, 1 থেকে 1,000 এর মধ্যে যেকোনো সংখ্যা মেলাতে চাই!
কম্পাইলার কম্পাইল টাইমে পরীক্ষা করে যে রেঞ্জটি খালি নয়, এবং যেহেতু রাস্ট কেবল char এবং সাংখ্যিক (numeric) মানের জন্য বলতে পারে একটি রেঞ্জ খালি কিনা, তাই রেঞ্জ কেবল সাংখ্যিক বা char মানের সাথে অনুমোদিত।
এখানে char মানের রেঞ্জ ব্যবহার করে একটি উদাহরণ দেওয়া হলো:
fn main() { let x = 'c'; match x { 'a'..='j' => println!("early ASCII letter"), 'k'..='z' => println!("late ASCII letter"), _ => println!("something else"), } }
রাস্ট বলতে পারে যে 'c' প্রথম প্যাটার্নের রেঞ্জের মধ্যে রয়েছে এবং early ASCII letter প্রিন্ট করে।
মান ভাঙার জন্য ডিস্ট্রাকচারিং (Destructuring to Break Apart Values)
আমরা struct, enum এবং tuple-কে ডিস্ট্রাকচার (destructure) বা ভাঙতে প্যাটার্ন ব্যবহার করতে পারি যাতে এই মানগুলোর বিভিন্ন অংশ ব্যবহার করা যায়। চলুন প্রতিটি মান নিয়ে আলোচনা করি।
Struct ডিস্ট্রাকচারিং (Destructuring Structs)
লিস্টিং ১৯-১২ একটি Point struct দেখায় যার দুটি ফিল্ড, x এবং y, আছে যা আমরা একটি 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 struct-এর x এবং y ফিল্ডের মানের সাথে মেলে। এই উদাহরণটি দেখায় যে প্যাটার্নের ভেরিয়েবলের নামগুলো struct-এর ফিল্ডের নামের সাথে মিলতে হবে এমন কোনো কথা নেই। তবে, কোন ভেরিয়েবল কোন ফিল্ড থেকে এসেছে তা মনে রাখা সহজ করার জন্য ভেরিয়েবলের নামগুলো ফিল্ডের নামের সাথে মেলানো একটি সাধারণ অভ্যাস। এই সাধারণ ব্যবহারের কারণে, এবং কারণ let Point { x: x, y: y } = p; লেখাতে অনেক পুনরাবৃত্তি রয়েছে, তাই রাস্টের struct ফিল্ড ম্যাচ করা প্যাটার্নগুলোর জন্য একটি শর্টহ্যান্ড আছে: আপনার কেবল struct ফিল্ডের নাম তালিকাভুক্ত করতে হবে, এবং প্যাটার্ন থেকে তৈরি ভেরিয়েবলগুলোর একই নাম থাকবে। লিস্টিং ১৯-১৩ লিস্টিং ১৯-১২-এর কোডের মতোই আচরণ করে, কিন্তু 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 struct থেকে মান ধারণ করে।
আমরা struct প্যাটার্নের অংশ হিসাবে সমস্ত ফিল্ডের জন্য ভেরিয়েবল তৈরি করার পরিবর্তে লিটারেল মান দিয়েও ডিস্ট্রাকচার করতে পারি। এটি আমাদের কিছু ফিল্ডকে নির্দিষ্ট মানের জন্য পরীক্ষা করার সুযোগ দেয় এবং অন্য ফিল্ডগুলো ডিস্ট্রাকচার করার জন্য ভেরিয়েবল তৈরি করতে দেয়।
লিস্টিং ১৯-১৪-এ, আমাদের একটি 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})"); } } }
প্রথম arm টি x অক্ষের উপর অবস্থিত যেকোনো পয়েন্টের সাথে মিলবে, এটি নির্দিষ্ট করে যে y ফিল্ডটি মিলবে যদি এর মান লিটারেল 0 এর সাথে মেলে। প্যাটার্নটি এখনও একটি x ভেরিয়েবল তৈরি করে যা আমরা এই arm-এর কোডে ব্যবহার করতে পারি।
একইভাবে, দ্বিতীয় arm টি y অক্ষের উপর যেকোনো পয়েন্টের সাথে মেলে, এটি নির্দিষ্ট করে যে x ফিল্ডটি মিলবে যদি এর মান 0 হয় এবং y ফিল্ডের মানের জন্য একটি y ভেরিয়েবল তৈরি করে। তৃতীয় arm টি কোনো লিটারেল নির্দিষ্ট করে না, তাই এটি অন্য যেকোনো Point-এর সাথে মেলে এবং x ও y উভয় ফিল্ডের জন্য ভেরিয়েবল তৈরি করে।
এই উদাহরণে, p মানটি দ্বিতীয় arm-এর সাথে মেলে কারণ x-এ 0 রয়েছে, তাই এই কোডটি On the y axis at 7 প্রিন্ট করবে।
মনে রাখবেন যে একটি match এক্সপ্রেশন প্রথম ম্যাচিং প্যাটার্ন খুঁজে পাওয়ার সাথে সাথে arm পরীক্ষা করা বন্ধ করে দেয়, তাই যদিও Point { x: 0, y: 0 } x অক্ষ এবং y অক্ষ উভয়তেই রয়েছে, এই কোডটি কেবল On the x axis at 0 প্রিন্ট করত।
Enum ডিস্ট্রাকচারিং (Destructuring Enums)
আমরা এই বইয়ে enum ডিস্ট্রাকচার করেছি (উদাহরণস্বরূপ, ৬ষ্ঠ অধ্যায়ের লিস্টিং ৬-৫), কিন্তু এখনো স্পষ্টভাবে আলোচনা করিনি যে একটি enum ডিস্ট্রাকচার করার প্যাটার্নটি enum-এর মধ্যে সংরক্ষিত ডেটা যেভাবে সংজ্ঞায়িত করা হয়েছে তার সাথে সামঞ্জস্যপূর্ণ। উদাহরণ হিসাবে, লিস্টিং ১৯-১৫-এ আমরা লিস্টিং ৬-২ থেকে Message enum ব্যবহার করি এবং একটি 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 color to red {r}, green {g}, and blue {b}"); } } }
এই কোডটি Change color to red 0, green 160, and blue 255 প্রিন্ট করবে। msg-এর মান পরিবর্তন করে অন্য arm-গুলোর কোড চলতে দেখুন।
Message::Quit-এর মতো কোনো ডেটা ছাড়া enum ভ্যারিয়েন্টগুলোর জন্য, আমরা মানটি আর ডিস্ট্রাকচার করতে পারি না। আমরা কেবল লিটারেল Message::Quit মানের সাথে ম্যাচ করতে পারি, এবং সেই প্যাটার্নে কোনো ভেরিয়েবল নেই।
Message::Move-এর মতো struct-এর মতো enum ভ্যারিয়েন্টগুলোর জন্য, আমরা struct ম্যাচ করার জন্য যে প্যাটার্ন নির্দিষ্ট করি তার মতো একটি প্যাটার্ন ব্যবহার করতে পারি। ভ্যারিয়েন্টের নামের পরে, আমরা কোঁকড়া বন্ধনী রাখি এবং তারপর ভেরিয়েবল সহ ফিল্ডগুলো তালিকাভুক্ত করি যাতে আমরা এই arm-এর কোডে ব্যবহারের জন্য টুকরোগুলো ভাঙতে পারি। এখানে আমরা লিস্টিং ১৯-১৩-এর মতো শর্টহ্যান্ড ফর্ম ব্যবহার করি।
Message::Write-এর মতো tuple-এর মতো enum ভ্যারিয়েন্টগুলোর জন্য, যা একটি উপাদান সহ একটি tuple ধারণ করে এবং Message::ChangeColor যা তিনটি উপাদান সহ একটি tuple ধারণ করে, প্যাটার্নটি tuple ম্যাচ করার জন্য আমরা যে প্যাটার্ন নির্দিষ্ট করি তার অনুরূপ। প্যাটার্নের ভেরিয়েবলের সংখ্যা আমরা যে ভ্যারিয়েন্টটি ম্যাচ করছি তার উপাদানের সংখ্যার সাথে অবশ্যই মিলতে হবে।
নেস্টেড Struct এবং Enum ডিস্ট্রাকচারিং (Destructuring Nested Structs and Enums)
এখন পর্যন্ত, আমাদের উদাহরণগুলো সবই এক স্তরে struct বা enum ম্যাচিং করেছে, কিন্তু ম্যাচিং নেস্টেড আইটেমগুলোতেও কাজ করতে পারে! উদাহরণস্বরূপ, আমরা ChangeColor মেসেজে RGB এবং HSV রঙ সমর্থন করার জন্য লিস্টিং ১৯-১৫-এর কোডটি রিফ্যাক্টর করতে পারি, যেমনটি লিস্টিং ১৯-১৬-তে দেখানো হয়েছে।
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 এক্সপ্রেশনের প্রথম arm-এর প্যাটার্নটি একটি Message::ChangeColor enum ভ্যারিয়েন্টের সাথে মেলে যা একটি Color::Rgb ভ্যারিয়েন্ট ধারণ করে; তারপর প্যাটার্নটি তিনটি ভেতরের i32 মানের সাথে বাইন্ড হয়। দ্বিতীয় arm-এর প্যাটার্নটিও একটি Message::ChangeColor enum ভ্যারিয়েন্টের সাথে মেলে, কিন্তু ভেতরের enum Color::Hsv-এর সাথে মেলে। আমরা এই জটিল শর্তগুলো একটি match এক্সপ্রেশনে নির্দিষ্ট করতে পারি, যদিও দুটি enum জড়িত।
Struct এবং Tuple ডিস্ট্রাকচারিং (Destructuring Structs and Tuples)
আমরা আরও জটিল উপায়ে ডিস্ট্রাকচারিং প্যাটার্নগুলো মিশ্রিত, ম্যাচ এবং নেস্ট করতে পারি। নিম্নলিখিত উদাহরণটি একটি জটিল ডিস্ট্রাকচার দেখায় যেখানে আমরা একটি tuple-এর ভিতরে struct এবং tuple নেস্ট করি এবং সমস্ত প্রিমিটিভ (primitive) মান বের করে আনি:
fn main() { struct Point { x: i32, y: i32, } let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 }); }
এই কোডটি আমাদের জটিল টাইপগুলোকে তাদের উপাদান অংশে ভাঙতে দেয় যাতে আমরা আগ্রহী মানগুলো আলাদাভাবে ব্যবহার করতে পারি।
প্যাটার্ন দিয়ে ডিস্ট্রাকচার করা মানগুলোর টুকরো ব্যবহার করার একটি সুবিধাজনক উপায়, যেমন একটি struct-এর প্রতিটি ফিল্ড থেকে মান, একে অপরের থেকে আলাদাভাবে।
একটি প্যাটার্নে মান উপেক্ষা করা (Ignoring Values in a Pattern)
আপনি দেখেছেন যে কখনও কখনও একটি প্যাটার্নে মান উপেক্ষা করা কার্যকর, যেমন একটি match-এর শেষ arm-এ, একটি ক্যাচ-অল পাওয়ার জন্য যা আসলে কিছুই করে না কিন্তু বাকি সমস্ত সম্ভাব্য মান বিবেচনা করে। একটি প্যাটার্নে সম্পূর্ণ মান বা মানের অংশ উপেক্ষা করার কয়েকটি উপায় রয়েছে: _ প্যাটার্ন ব্যবহার করে (যা আপনি দেখেছেন), অন্য প্যাটার্নের মধ্যে _ প্যাটার্ন ব্যবহার করে, একটি আন্ডারস্কোর দিয়ে শুরু হওয়া নাম ব্যবহার করে, অথবা .. ব্যবহার করে একটি মানের বাকি অংশ উপেক্ষা করার জন্য। চলুন দেখি কিভাবে এবং কেন এই প্রতিটি প্যাটার্ন ব্যবহার করতে হয়।
_ দিয়ে একটি সম্পূর্ণ মান (An Entire Value with _)
আমরা আন্ডারস্কোরকে একটি ওয়াইল্ডকার্ড প্যাটার্ন হিসাবে ব্যবহার করেছি যা যেকোনো মানের সাথে মিলবে কিন্তু মানের সাথে বাইন্ড হবে না। এটি একটি match এক্সপ্রেশনের শেষ arm হিসাবে বিশেষভাবে কার্যকর, তবে আমরা এটি ফাংশন প্যারামিটার সহ যেকোনো প্যাটার্নেও ব্যবহার করতে পারি, যেমনটি লিস্টিং ১৯-১৭-তে দেখানো হয়েছে।
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 প্রিন্ট করবে।
বেশিরভাগ ক্ষেত্রে যখন আপনার আর কোনো নির্দিষ্ট ফাংশন প্যারামিটারের প্রয়োজন হয় না, তখন আপনি সিগনেচারটি পরিবর্তন করে অব্যবহৃত প্যারামিটারটি সরিয়ে ফেলবেন। একটি ফাংশন প্যারামিটার উপেক্ষা করা বিশেষভাবে কার্যকর হতে পারে এমন ক্ষেত্রে যখন, উদাহরণস্বরূপ, আপনি একটি trait ইমপ্লিমেন্ট করছেন যেখানে আপনার একটি নির্দিষ্ট টাইপ সিগনেচার প্রয়োজন কিন্তু আপনার ইমপ্লিমেন্টেশনের ফাংশন বডিতে প্যারামিটারগুলোর একটির প্রয়োজন নেই। তখন আপনি অব্যবহৃত ফাংশন প্যারামিটার সম্পর্কে কম্পাইলারের সতর্কতা এড়াতে পারেন, যেমনটি আপনি একটি নাম ব্যবহার করলে পেতেন।
নেস্টেড _ দিয়ে একটি মানের অংশ (Parts of a Value with a Nested _)
আমরা একটি মানের কেবল একটি অংশ উপেক্ষা করার জন্য অন্য প্যাটার্নের ভিতরে _ ব্যবহার করতে পারি, উদাহরণস্বরূপ, যখন আমরা একটি মানের কেবল একটি অংশ পরীক্ষা করতে চাই কিন্তু সংশ্লিষ্ট কোডে অন্য অংশগুলোর কোনো ব্যবহার নেই। লিস্টিং ১৯-১৮ একটি সেটিং-এর মান পরিচালনার জন্য দায়ী কোড দেখায়। ব্যবসায়িক প্রয়োজনীয়তা হলো ব্যবহারকারীকে একটি বিদ্যমান কাস্টমাইজেশন ওভাররাইট করার অনুমতি দেওয়া উচিত নয় কিন্তু সেটিংটি আনসেট করতে এবং বর্তমানে আনসেট থাকলে একটি মান দিতে পারে।
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) প্রিন্ট করবে। প্রথম ম্যাচ arm-এ, আমাদের Some ভ্যারিয়েন্টের কোনোটির ভিতরের মানের সাথে ম্যাচ বা ব্যবহার করার প্রয়োজন নেই, কিন্তু আমাদের সেই কেসটি পরীক্ষা করতে হবে যখন setting_value এবং new_setting_value উভয়ই Some ভ্যারিয়েন্ট। সেই ক্ষেত্রে, আমরা setting_value পরিবর্তন না করার কারণ প্রিন্ট করি, এবং এটি পরিবর্তন হয় না।
অন্য সব ক্ষেত্রে (যদি setting_value বা new_setting_value যেকোনো একটি None হয়) যা দ্বিতীয় arm-এর _ প্যাটার্ন দ্বারা প্রকাশ করা হয়, আমরা new_setting_value-কে setting_value হতে দিতে চাই।
আমরা নির্দিষ্ট মান উপেক্ষা করার জন্য একটি প্যাটার্নের মধ্যে একাধিক জায়গায় আন্ডারস্কোর ব্যবহার করতে পারি। লিস্টিং ১৯-১৯ পাঁচটি আইটেমের একটি tuple-এর দ্বিতীয় এবং চতুর্থ মান উপেক্ষা করার একটি উদাহরণ দেখায়।
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 মানগুলো উপেক্ষা করা হবে।
_ দিয়ে নাম শুরু করে একটি অব্যবহৃত ভেরিয়েবল (An Unused Variable by Starting Its Name with _)
আপনি যদি একটি ভেরিয়েবল তৈরি করেন কিন্তু কোথাও ব্যবহার না করেন, রাস্ট সাধারণত একটি সতর্কতা জারি করবে কারণ একটি অব্যবহৃত ভেরিয়েবল একটি বাগ হতে পারে। তবে, কখনও কখনও এমন একটি ভেরিয়েবল তৈরি করা কার্যকর হয় যা আপনি এখনো ব্যবহার করবেন না, যেমন যখন আপনি প্রোটোটাইপিং করছেন বা সবেমাত্র একটি প্রকল্প শুরু করছেন। এই পরিস্থিতিতে, আপনি ভেরিয়েবলের নামটি একটি আন্ডারস্কোর দিয়ে শুরু করে রাস্টকে বলতে পারেন যাতে সে আপনাকে অব্যবহৃত ভেরিয়েবল সম্পর্কে সতর্ক না করে। লিস্টিং ১৯-২০-এ, আমরা দুটি অব্যবহৃত ভেরিয়েবল তৈরি করি, কিন্তু যখন আমরা এই কোডটি কম্পাইল করি, তখন আমাদের কেবল একটি সম্পর্কে সতর্কতা পাওয়া উচিত।
fn main() { let _x = 5; let y = 10; }
এখানে, আমরা y ভেরিয়েবল ব্যবহার না করার জন্য একটি সতর্কতা পাই, কিন্তু _x ব্যবহার না করার জন্য কোনো সতর্কতা পাই না।
লক্ষ্য করুন যে কেবল _ ব্যবহার করা এবং একটি আন্ডারস্কোর দিয়ে শুরু হওয়া নাম ব্যবহার করার মধ্যে একটি সূক্ষ্ম পার্থক্য রয়েছে। _x সিনট্যাক্সটি এখনও মানটিকে ভেরিয়েবলের সাথে বাইন্ড করে, যেখানে _ মোটেই বাইন্ড করে না। এই পার্থক্যটি গুরুত্বপূর্ণ এমন একটি কেস দেখানোর জন্য, লিস্টিং ১৯-২১ আমাদের একটি এরর দেবে।
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
আমরা একটি এরর পাব কারণ s মানটি এখনও _s-এ মুভ করা হবে, যা আমাদের s পুনরায় ব্যবহার করতে বাধা দেয়। তবে, আন্ডারস্কোর নিজে থেকে ব্যবহার করলে তা কখনও মানের সাথে বাইন্ড করে না। লিস্টিং ১৯-২২ কোনো এরর ছাড়াই কম্পাইল হবে কারণ s _-এ মুভ হয় না।
fn main() { let s = Some(String::from("Hello!")); if let Some(_) = s { println!("found a string"); } println!("{s:?}"); }``` </Listing> এই কোডটি ঠিকঠাক কাজ করে কারণ আমরা কখনও `s`-কে কিছুর সাথে বাইন্ড করি না; এটি মুভ হয় না। <a id="ignoring-remaining-parts-of-a-value-with-"></a> ### `..` দিয়ে একটি মানের বাকি অংশ (_Remaining Parts of a Value with `..`_) অনেক অংশ সহ মানগুলোর জন্য, আমরা নির্দিষ্ট অংশ ব্যবহার করতে এবং বাকিগুলো উপেক্ষা করতে `..` সিনট্যাক্স ব্যবহার করতে পারি, প্রতিটি উপেক্ষা করা মানের জন্য আন্ডারস্কোর তালিকাভুক্ত করার প্রয়োজন এড়িয়ে। `..` প্যাটার্নটি একটি মানের যেকোনো অংশ উপেক্ষা করে যা আমরা প্যাটার্নের বাকি অংশে স্পষ্টভাবে ম্যাচ করিনি। লিস্টিং ১৯-২৩-এ, আমাদের একটি `Point` struct আছে যা ত্রি-মাত্রিক স্থানে একটি স্থানাঙ্ক ধারণ করে। `match` এক্সপ্রেশনে, আমরা কেবল `x` স্থানাঙ্কের উপর কাজ করতে চাই এবং `y` ও `z` ফিল্ডের মানগুলো উপেক্ষা করতে চাই। <Listing number="19-23" caption="`..` ব্যবহার করে `x` ব্যতীত একটি `Point`-এর সমস্ত ফিল্ড উপেক্ষা করা"> ```rust 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: _ তালিকাভুক্ত করার চেয়ে দ্রুততর, বিশেষ করে যখন আমরা এমন struct নিয়ে কাজ করি যার অনেক ফিল্ড আছে এবং যেখানে কেবল এক বা দুটি ফিল্ড প্রাসঙ্গিক।
.. সিনট্যাক্সটি যতগুলো মান প্রয়োজন ততগুলো পর্যন্ত প্রসারিত হবে। লিস্টিং ১৯-২৪ দেখায় কিভাবে একটি tuple-এর সাথে .. ব্যবহার করতে হয়।
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {first}, {last}"); } } }
এই কোডে, প্রথম এবং শেষ মান first এবং last দিয়ে ম্যাচ করা হয়। .. মাঝের সবকিছু ম্যাচ এবং উপেক্ষা করবে।
তবে, .. ব্যবহার অবশ্যই দ্ব্যর্থহীন হতে হবে। যদি এটি অস্পষ্ট হয় যে কোন মানগুলো ম্যাচ করার জন্য এবং কোনগুলো উপেক্ষা করা উচিত, রাস্ট আমাদের একটি এরর দেবে। লিস্টিং ১৯-২৫ .. দ্ব্যর্থকভাবে ব্যবহার করার একটি উদাহরণ দেখায়, তাই এটি কম্পাইল হবে না।
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
যখন আমরা এই উদাহরণটি কম্পাইল করি, আমরা এই এররটি পাই:
$ 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
রাস্টের পক্ষে নির্ধারণ করা অসম্ভব যে tuple-এ second-এর সাথে একটি মান ম্যাচ করার আগে কতগুলো মান উপেক্ষা করতে হবে এবং তারপর আরও কতগুলো মান উপেক্ষা করতে হবে। এই কোডের মানে হতে পারে যে আমরা 2 উপেক্ষা করতে চাই, second-কে 4-এ বাইন্ড করতে চাই, এবং তারপর 8, 16, এবং 32 উপেক্ষা করতে চাই; অথবা আমরা 2 এবং 4 উপেক্ষা করতে চাই, second-কে 8-এ বাইন্ড করতে চাই, এবং তারপর 16 এবং 32 উপেক্ষা করতে চাই; ইত্যাদি। second ভেরিয়েবলের নামটি রাস্টের কাছে কোনো বিশেষ অর্থ বহন করে না, তাই আমরা একটি কম্পাইলার এরর পাই কারণ দুটি জায়গায় এভাবে .. ব্যবহার করা দ্ব্যর্থক।
ম্যাচ গার্ডের সাথে অতিরিক্ত কন্ডিশনাল (Extra Conditionals with Match Guards)
একটি ম্যাচ গার্ড (match guard) হলো একটি অতিরিক্ত if শর্ত, যা একটি match arm-এর প্যাটার্নের পরে নির্দিষ্ট করা হয়, যা সেই arm-টি বেছে নেওয়ার জন্য অবশ্যই মিলতে হবে। ম্যাচ গার্ডগুলো একটি প্যাটার্ন একাই যা প্রকাশ করতে পারে তার চেয়ে জটিল ধারণা প্রকাশ করার জন্য কার্যকর। তবে, লক্ষ্য করুন যে এগুলো কেবল match এক্সপ্রেশনে উপলব্ধ, if let বা while let এক্সপ্রেশনে নয়।
শর্তটি প্যাটার্নে তৈরি ভেরিয়েবল ব্যবহার করতে পারে। লিস্টিং ১৯-২৬ একটি match দেখায় যেখানে প্রথম arm-এর প্যাটার্ন Some(x) এবং একটি ম্যাচ গার্ড if x % 2 == 0 (যা সংখ্যাটি জোড় হলে true হবে) রয়েছে।
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-কে প্রথম arm-এর প্যাটার্নের সাথে তুলনা করা হয়, তখন এটি মেলে কারণ Some(4) Some(x)-এর সাথে মেলে। তারপর ম্যাচ গার্ড পরীক্ষা করে যে x-কে 2 দিয়ে ভাগ করার ভাগশেষ 0 এর সমান কিনা, এবং যেহেতু তা হয়, তাই প্রথম arm-টি নির্বাচন করা হয়।
যদি num Some(5) হতো, তবে প্রথম arm-এর ম্যাচ গার্ডটি false হতো কারণ 5-কে 2 দিয়ে ভাগ করার ভাগশেষ 1, যা 0 এর সমান নয়। রাস্ট তখন দ্বিতীয় arm-এ যেত, যা মিলত কারণ দ্বিতীয় arm-এর কোনো ম্যাচ গার্ড নেই এবং তাই যেকোনো Some ভ্যারিয়েন্টের সাথে মেলে।
if x % 2 == 0 শর্তটি একটি প্যাটার্নের মধ্যে প্রকাশ করার কোনো উপায় নেই, তাই ম্যাচ গার্ড আমাদের এই লজিকটি প্রকাশ করার ক্ষমতা দেয়। এই অতিরিক্ত প্রকাশক্ষমতার অসুবিধা হলো কম্পাইলার ম্যাচ গার্ড এক্সপ্রেশন জড়িত থাকলে এক্সহস্টিভনেস (exhaustiveness) পরীক্ষা করার চেষ্টা করে না।
লিস্টিং ১৯-১১-এ, আমরা উল্লেখ করেছি যে আমরা আমাদের প্যাটার্ন-শ্যাডোইং সমস্যা সমাধানের জন্য ম্যাচ গার্ড ব্যবহার করতে পারি। মনে করুন, আমরা match এক্সপ্রেশনের বাইরের ভেরিয়েবল ব্যবহার না করে প্যাটার্নের ভিতরে একটি নতুন ভেরিয়েবল তৈরি করেছিলাম। সেই নতুন ভেরিয়েবলের মানে হলো আমরা বাইরের ভেরিয়েবলের মানের বিরুদ্ধে পরীক্ষা করতে পারিনি। লিস্টিং ১৯-২৭ দেখায় কিভাবে আমরা এই সমস্যাটি সমাধান করার জন্য একটি ম্যাচ গার্ড ব্যবহার করতে পারি।
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) প্রিন্ট করবে। দ্বিতীয় ম্যাচ arm-এর প্যাটার্নটি একটি নতুন y ভেরিয়েবল প্রবর্তন করে না যা বাইরের y-কে শ্যাডো করবে, যার মানে আমরা ম্যাচ গার্ডে বাইরের y ব্যবহার করতে পারি। প্যাটার্নটিকে Some(y) হিসাবে নির্দিষ্ট করার পরিবর্তে, যা বাইরের y-কে শ্যাডো করত, আমরা Some(n) নির্দিষ্ট করি। এটি একটি নতুন n ভেরিয়েবল তৈরি করে যা কিছুই শ্যাডো করে না কারণ match-এর বাইরে কোনো n ভেরিয়েবল নেই।
ম্যাচ গার্ড if n == y একটি প্যাটার্ন নয় এবং তাই নতুন ভেরিয়েবল প্রবর্তন করে না। এই y হলো বাইরের y এবং এটি শ্যাডো করা নতুন y নয়, এবং আমরা n-কে y-এর সাথে তুলনা করে বাইরের y-এর সমান মান খুঁজতে পারি।
আপনি একাধিক প্যাটার্ন নির্দিষ্ট করার জন্য একটি ম্যাচ গার্ডে or অপারেটর | ব্যবহার করতে পারেন; ম্যাচ গার্ড শর্তটি সমস্ত প্যাটার্নে প্রযোজ্য হবে। লিস্টিং ১৯-২৮ | ব্যবহারকারী একটি প্যাটার্নকে একটি ম্যাচ গার্ডের সাথে একত্রিত করার সময় প্রেসিডেন্স (precedence) দেখায়। এই উদাহরণের গুরুত্বপূর্ণ অংশ হলো 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"), } }
ম্যাচ শর্তটি বলে যে arm-টি কেবল তখনই মিলবে যদি x-এর মান 4, 5, বা 6-এর সমান হয় এবং যদি y true হয়। যখন এই কোডটি চলে, প্রথম arm-এর প্যাটার্নটি মেলে কারণ x 4, কিন্তু ম্যাচ গার্ড if y false, তাই প্রথম arm-টি বেছে নেওয়া হয় না। কোডটি দ্বিতীয় arm-এ চলে যায়, যা মেলে, এবং এই প্রোগ্রামটি no প্রিন্ট করে। কারণ হলো if শর্তটি পুরো প্যাটার্ন 4 | 5 | 6-এর উপর প্রযোজ্য, কেবল শেষ মান 6-এর উপর নয়। অন্য কথায়, একটি প্যাটার্নের সাথে একটি ম্যাচ গার্ডের প্রেসিডেন্স এভাবে আচরণ করে:
(4 | 5 | 6) if y => ...
এর পরিবর্তে:
4 | 5 | (6 if y) => ...
কোডটি চালানোর পরে, প্রেসিডেন্স আচরণটি স্পষ্ট: যদি ম্যাচ গার্ডটি কেবল | অপারেটর ব্যবহার করে নির্দিষ্ট করা মানগুলোর তালিকার চূড়ান্ত মানের উপর প্রয়োগ করা হতো, তবে arm-টি মিলত এবং প্রোগ্রামটি yes প্রিন্ট করত।
@ বাইন্ডিং (@ Bindings)
at অপারেটর @ আমাদের একটি ভেরিয়েবল তৈরি করতে দেয় যা একটি মান ধারণ করে একই সময়ে যখন আমরা সেই মানটিকে একটি প্যাটার্ন ম্যাচের জন্য পরীক্ষা করছি। লিস্টিং ১৯-২৯-এ, আমরা পরীক্ষা করতে চাই যে একটি Message::Hello id ফিল্ডটি 3..=7 রেঞ্জের মধ্যে আছে কিনা। আমরা মানটিকে id ভেরিয়েবলে বাইন্ড করতে চাই যাতে আমরা arm-এর সাথে যুক্ত কোডে এটি ব্যবহার করতে পারি।
fn main() { enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id @ 3..=7 } => { println!("Found an id in range: {id}") } 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 প্রিন্ট করবে। id @ নির্দিষ্ট করে 3..=7 রেঞ্জের আগে, আমরা রেঞ্জ প্যাটার্নের সাথে মিলে যাওয়া যেকোনো মানকে id নামের একটি ভেরিয়েবলে ক্যাপচার করছি এবং একই সাথে পরীক্ষা করছি যে মানটি রেঞ্জ প্যাটার্নের সাথে মিলেছে কিনা।
দ্বিতীয় arm-এ, যেখানে আমরা কেবল প্যাটার্নে একটি রেঞ্জ নির্দিষ্ট করেছি, arm-এর সাথে যুক্ত কোডে এমন কোনো ভেরিয়েবল নেই যা id ফিল্ডের আসল মান ধারণ করে। id ফিল্ডের মান 10, 11, বা 12 হতে পারত, কিন্তু সেই প্যাটার্নের সাথে যাওয়া কোড জানে না কোনটি। প্যাটার্ন কোডটি id ফিল্ড থেকে মান ব্যবহার করতে সক্ষম নয়, কারণ আমরা id মানটি একটি ভেরিয়েবলে সংরক্ষণ করিনি।
শেষ arm-এ, যেখানে আমরা একটি রেঞ্জ ছাড়াই একটি ভেরিয়েবল নির্দিষ্ট করেছি, আমাদের arm-এর কোডে ব্যবহারের জন্য id নামের একটি ভেরিয়েবলে মানটি উপলব্ধ রয়েছে। কারণ হলো আমরা struct ফিল্ড শর্টহ্যান্ড সিনট্যাক্স ব্যবহার করেছি। কিন্তু আমরা এই arm-এ id ফিল্ডের মানের উপর কোনো পরীক্ষা প্রয়োগ করিনি, যেমনটি আমরা প্রথম দুটি arm-এর সাথে করেছি: যেকোনো মান এই প্যাটার্নের সাথে মিলবে।
@ ব্যবহার করা আমাদের একটি মান পরীক্ষা করতে এবং এটি একটি ভেরিয়েবলে একটি প্যাটার্নের মধ্যে সংরক্ষণ করতে দেয়।
সারাংশ (Summary)
রাস্টের প্যাটার্নগুলো বিভিন্ন ধরণের ডেটার মধ্যে পার্থক্য করার জন্য খুব কার্যকর। match এক্সপ্রেশনে ব্যবহার করা হলে, রাস্ট নিশ্চিত করে যে আপনার প্যাটার্নগুলো প্রতিটি সম্ভাব্য মান কভার করে, নতুবা আপনার প্রোগ্রাম কম্পাইল হবে না। let স্টেটমেন্ট এবং ফাংশন প্যারামিটারে প্যাটার্নগুলো সেই কনস্ট্রাক্টগুলোকে আরও কার্যকর করে তোলে, মানগুলোকে ছোট অংশে ডিস্ট্রাকচার করতে এবং সেই অংশগুলোকে ভেরিয়েবলে অ্যাসাইন করতে সক্ষম করে। আমরা আমাদের প্রয়োজন অনুসারে সহজ বা জটিল প্যাটার্ন তৈরি করতে পারি।
এরপর, বইয়ের উপশেষ অধ্যায়ের জন্য, আমরা রাস্টের বিভিন্ন বৈশিষ্ট্যের কিছু উন্নত দিক দেখব।