জেনেরিক টাইপ, ট্রেইট এবং লাইফটাইম
প্রত্যেক প্রোগ্রামিং ল্যাঙ্গুয়েজেই ধারণার পুনরাবৃত্তি (duplication of concepts) কার্যকরভাবে পরিচালনা করার জন্য বিভিন্ন টুল থাকে। Rust-এ, এরকম একটি টুল হলো generics: যা concrete type বা অন্যান্য properties-এর জন্য ব্যবহৃত abstract stand-ins। কোড কম্পাইল এবং রান করার সময় generics-এর জায়গায় কী থাকবে তা না জেনেই আমরা তাদের আচরণ বা অন্য generics-এর সাথে তাদের সম্পর্ক প্রকাশ করতে পারি।
ফাংশনগুলো কোনো concrete type যেমন i32
বা String
-এর পরিবর্তে কোনো generic type-এর প্যারামিটার নিতে পারে, ঠিক যেমনভাবে তারা অজানা মানসহ প্যারামিটার নিয়ে একাধিক concrete value-এর উপর একই কোড চালায়। সত্যি বলতে, আমরা ইতোমধ্যে চ্যাপ্টার ৬-এ Option<T>
, চ্যাপ্টার ৮-এ Vec<T>
এবং HashMap<K, V>
, এবং চ্যাপ্টার ৯-এ Result<T, E>
-এর সাথে generics ব্যবহার করেছি। এই চ্যাপ্টারে, আপনি generics ব্যবহার করে কীভাবে নিজের টাইপ, ফাংশন এবং মেথড ডিফাইন করবেন তা শিখবেন!
প্রথমে আমরা কোডের পুনরাবৃত্তি কমাতে কীভাবে একটি ফাংশন এক্সট্র্যাক্ট করা যায় তা পর্যালোচনা করব। এরপর আমরা একই কৌশল ব্যবহার করে দুটি ফাংশন থেকে একটি জেনেরিক ফাংশন তৈরি করব, যেখানে ফাংশন দুটির মধ্যে শুধুমাত্র তাদের প্যারামিটারের টাইপ ভিন্ন থাকবে। আমরা struct এবং enum ডেফিনিশনে কীভাবে জেনেরিক টাইপ ব্যবহার করতে হয় তাও ব্যাখ্যা করব।
এরপর আপনি traits ব্যবহার করে কীভাবে জেনেরিক উপায়ে আচরণ (behavior) ডিফাইন করতে হয় তা শিখবেন। আপনি generic type-এর সাথে traits যুক্ত করে একটি generic type-কে সীমাবদ্ধ করতে পারেন, যাতে এটি যেকোনো টাইপের পরিবর্তে শুধুমাত্র নির্দিষ্ট আচরণযুক্ত টাইপ গ্রহণ করে।
সবশেষে, আমরা lifetimes নিয়ে আলোচনা করব: এটি এক ধরনের generics যা কম্পাইলারকে রেফারেন্সগুলো একে অপরের সাথে কীভাবে সম্পর্কিত সে সম্পর্কে তথ্য দেয়। Lifetimes ব্যবহার করে আমরা কম্পাইলারকে ধার করা মান (borrowed values) সম্পর্কে যথেষ্ট তথ্য দিতে পারি, যাতে এটি নিশ্চিত করতে পারে যে রেফারেন্সগুলো আমাদের সাহায্য ছাড়াই যতটা সম্ভব তার চেয়ে বেশি পরিস্থিতিতে ভ্যালিড থাকবে।
ফাংশন এক্সট্র্যাক্ট করে কোডের পুনরাবৃত্তি দূর করা
Generics আমাদের কোডের পুনরাবৃত্তি দূর করার জন্য নির্দিষ্ট টাইপের পরিবর্তে একটি প্লেসহোল্ডার ব্যবহার করার সুযোগ দেয় যা একাধিক টাইপকে উপস্থাপন করে। Generics সিনট্যাক্সে যাওয়ার আগে, চলুন প্রথমে দেখি কীভাবে জেনেরিক টাইপ ব্যবহার না করে কোডের পুনরাবৃত্তি দূর করা যায়। এর জন্য আমরা একটি ফাংশন এক্সট্র্যাক্ট করব যা নির্দিষ্ট মানের পরিবর্তে এমন একটি প্লেসহোল্ডার ব্যবহার করবে যা একাধিক মানকে উপস্থাপন করে। তারপর আমরা একই কৌশল প্রয়োগ করে একটি জেনেরিক ফাংশন এক্সট্র্যাক্ট করব! ডুপ্লিকেট কোড চিনে তাকে কীভাবে একটি ফাংশনে এক্সট্র্যাক্ট করা যায় তা দেখলে, আপনি ডুপ্লিকেট কোড চেনা শুরু করবেন যেখানে generics ব্যবহার করা যেতে পারে।
আমরা লিস্টিং ১০-১ এর একটি ছোট প্রোগ্রাম দিয়ে শুরু করব যা একটি তালিকা থেকে সবচেয়ে বড় সংখ্যাটি খুঁজে বের করে।
fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!("The largest number is {largest}"); assert_eq!(*largest, 100); }
আমরা number_list
ভ্যারিয়েবলে পূর্ণসংখ্যার একটি তালিকা রাখি এবং তালিকার প্রথম সংখ্যার একটি রেফারেন্স largest
নামের একটি ভ্যারিয়েবলে রাখি। তারপর আমরা তালিকার সমস্ত সংখ্যার উপর দিয়ে ইটারেট করি, এবং যদি বর্তমান সংখ্যাটি largest
-এ থাকা সংখ্যার চেয়ে বড় হয়, আমরা সেই ভ্যারিয়েবলের রেফারেন্সটি প্রতিস্থাপন করি। কিন্তু, যদি বর্তমান সংখ্যাটি এখন পর্যন্ত দেখা সবচেয়ে বড় সংখ্যার চেয়ে ছোট বা সমান হয়, ভ্যারিয়েবলটি পরিবর্তন হয় না এবং কোডটি তালিকার পরবর্তী সংখ্যায় চলে যায়। তালিকার সমস্ত সংখ্যা বিবেচনা করার পর, largest
-কে সবচেয়ে বড় সংখ্যাটিকে রেফার করা উচিত, যা এই ক্ষেত্রে ১০০।
এখন আমাদের দুটি ভিন্ন সংখ্যার তালিকা থেকে সবচেয়ে বড় সংখ্যাটি খুঁজে বের করার দায়িত্ব দেওয়া হয়েছে। এটি করার জন্য, আমরা লিস্টিং ১০-১ এর কোডটি ডুপ্লিকেট করতে পারি এবং প্রোগ্রামের দুটি ভিন্ন জায়গায় একই লজিক ব্যবহার করতে পারি, যেমনটি লিস্টিং ১০-২ এ দেখানো হয়েছে।
fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!("The largest number is {largest}"); let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!("The largest number is {largest}"); }
যদিও এই কোডটি কাজ করে, কোড ডুপ্লিকেট করা ক্লান্তিকর এবং ভুল হওয়ার সম্ভাবনা থাকে। আমরা যখন কোড পরিবর্তন করতে চাই, তখন একাধিক জায়গায় এটি আপডেট করার কথাও মনে রাখতে হয়।
এই পুনরাবৃত্তি দূর করার জন্য, আমরা একটি অ্যাবস্ট্র্যাকশন তৈরি করব একটি ফাংশন ডিফাইন করে যা প্যারামিটার হিসাবে পাস করা যেকোনো পূর্ণসংখ্যার তালিকার উপর কাজ করে। এই সমাধানটি আমাদের কোডকে আরও পরিষ্কার করে এবং একটি তালিকা থেকে সবচেয়ে বড় সংখ্যা খুঁজে বের করার ধারণাটিকে অ্যাবস্ট্র্যাক্টভাবে প্রকাশ করতে দেয়।
লিস্টিং ১০-৩ এ, আমরা সবচেয়ে বড় সংখ্যা খোঁজার কোডটিকে largest
নামের একটি ফাংশনে এক্সট্র্যাক্ট করেছি। তারপর আমরা লিস্টিং ১০-২ এর দুটি তালিকা থেকে সবচেয়ে বড় সংখ্যাটি খুঁজে বের করার জন্য ফাংশনটিকে কল করি। আমরা ভবিষ্যতে আমাদের কাছে থাকা যেকোনো i32
মানের তালিকার জন্যও ফাংশনটি ব্যবহার করতে পারতাম।
fn largest(list: &[i32]) -> &i32 { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest } fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {result}"); assert_eq!(*result, 100); let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8]; let result = largest(&number_list); println!("The largest number is {result}"); assert_eq!(*result, 6000); }
largest
ফাংশনটির list
নামে একটি প্যারামিটার রয়েছে, যা আমরা ফাংশনে পাস করতে পারি এমন যেকোনো i32
মানের concrete slice-কে প্রতিনিধিত্ব করে। ফলস্বরূপ, যখন আমরা ফাংশনটি কল করি, কোডটি আমাদের পাস করা নির্দিষ্ট মানগুলোর উপর চলে।
সংক্ষেপে, লিস্টিং ১০-২ থেকে লিস্টিং ১০-৩ এ কোড পরিবর্তন করার জন্য আমরা যে পদক্ষেপগুলো নিয়েছি তা হলো:
- ডুপ্লিকেট কোড শনাক্ত করুন।
- ডুপ্লিকেট কোডটি ফাংশনের বডিতে এক্সট্র্যাক্ট করুন, এবং ফাংশন সিগনেচারে সেই কোডের ইনপুট এবং রিটার্ন ভ্যালু উল্লেখ করুন।
- ডুপ্লিকেট কোডের দুটি ইনস্ট্যান্সকে ফাংশন কল করার জন্য আপডেট করুন।
এরপর, আমরা কোডের পুনরাবৃত্তি কমাতে generics-এর সাথে এই একই পদক্ষেপগুলো ব্যবহার করব। ঠিক যেমন ফাংশন বডি নির্দিষ্ট মানের পরিবর্তে একটি অ্যাবস্ট্র্যাক্ট list
-এর উপর কাজ করতে পারে, তেমনি generics কোডকে অ্যাবস্ট্র্যাক্ট টাইপের উপর কাজ করার অনুমতি দেয়।
উদাহরণস্বরূপ, ধরুন আমাদের দুটি ফাংশন ছিল: একটি যা i32
মানের একটি স্লাইস থেকে সবচেয়ে বড় আইটেম খুঁজে বের করে এবং অন্যটি যা char
মানের একটি স্লাইস থেকে সবচেয়ে বড় আইটেম খুঁজে বের করে। আমরা কীভাবে সেই পুনরাবৃত্তি দূর করব? চলুন খুঁজে বের করা যাক