জেনেরিক টাইপ, ট্রেইট এবং লাইফটাইম (Generic Types, Traits, and Lifetimes)

প্রায় প্রতিটি প্রোগ্রামিং ল্যাঙ্গুয়েজের ধারণার ডুপ্লিকেশন (duplication) কার্যকরভাবে পরিচালনা করার জন্য টুল রয়েছে। Rust-এ, এই ধরনের একটি টুল হল জেনেরিকস (generics): কংক্রিট টাইপ বা অন্যান্য বৈশিষ্ট্যের জন্য অ্যাবস্ট্রাক্ট স্ট্যান্ড-ইন (abstract stand-ins)। কোড কম্পাইল এবং রান করার সময় তাদের জায়গায় কী থাকবে তা না জেনেই আমরা জেনেরিকগুলোর আচরণ বা অন্যান্য জেনেরিকের সাথে তারা কীভাবে সম্পর্কিত তা প্রকাশ করতে পারি।

ফাংশনগুলো i32 বা String-এর মতো কংক্রিট টাইপের পরিবর্তে কিছু জেনেরিক টাইপের প্যারামিটার নিতে পারে, একইভাবে তারা একাধিক কংক্রিট মানের উপর একই কোড চালানোর জন্য অজানা মান সহ প্যারামিটার নেয়। প্রকৃতপক্ষে, আমরা ইতিমধ্যেই চ্যাপ্টার ৬-এ Option<T>, চ্যাপ্টার ৮-এ Vec<T> এবং HashMap<K, V>, এবং চ্যাপ্টার ৯-এ Result<T, E>-এর সাথে জেনেরিক ব্যবহার করেছি। এই চ্যাপ্টারে, আপনি শিখবেন কিভাবে জেনেরিক ব্যবহার করে আপনার নিজস্ব টাইপ, ফাংশন এবং মেথড সংজ্ঞায়িত করতে হয়!

প্রথমে আমরা কোড ডুপ্লিকেশন কমাতে একটি ফাংশন কীভাবে এক্সট্র্যাক্ট (extract) করতে হয় তা পর্যালোচনা করব। তারপর আমরা একই কৌশল ব্যবহার করে দুটি ফাংশন থেকে একটি জেনেরিক ফাংশন তৈরি করব যা শুধুমাত্র তাদের প্যারামিটারের টাইপের ক্ষেত্রে ভিন্ন। আমরা স্ট্রাকট এবং এনাম সংজ্ঞায় জেনেরিক টাইপ কীভাবে ব্যবহার করতে হয় তাও ব্যাখ্যা করব।

তারপর আপনি শিখবেন কিভাবে একটি জেনেরিক উপায়ে আচরণ সংজ্ঞায়িত করতে ট্রেইট (traits) ব্যবহার করতে হয়। আপনি জেনেরিক টাইপের সাথে ট্রেইটগুলোকে একত্রিত করে একটি জেনেরিক টাইপকে সীমাবদ্ধ করতে পারেন যাতে এটি শুধুমাত্র সেই টাইপগুলো গ্রহণ করে যাদের একটি নির্দিষ্ট আচরণ রয়েছে, যেকোনো টাইপের পরিবর্তে।

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

একটি ফাংশন এক্সট্র্যাক্ট করে ডুপ্লিকেশন অপসারণ করা (Removing Duplication by Extracting a Function)

কোড ডুপ্লিকেশন অপসারণ করতে আমরা জেনেরিক টাইপ ব্যবহার করে একটি ফাংশন তৈরি করব। জেনেরিক সিনট্যাক্সে (syntax) প্রবেশ করার আগে, আসুন প্রথমে দেখি কিভাবে জেনেরিক টাইপ ব্যবহার না করে ডুপ্লিকেশন অপসারণ করা যায়, এমন একটি ফাংশন এক্সট্র্যাক্ট করে যা নির্দিষ্ট মানগুলোকে একটি প্লেসহোল্ডার দিয়ে প্রতিস্থাপন করে যা একাধিক মান উপস্থাপন করে। তারপর আমরা একটি জেনেরিক ফাংশন এক্সট্র্যাক্ট করার জন্য একই কৌশল প্রয়োগ করব! কিভাবে ডুপ্লিকেট কোড শনাক্ত করতে হয় যা আপনি একটি ফাংশনে এক্সট্র্যাক্ট করতে পারেন, তা দেখে আপনি জেনেরিক ব্যবহার করতে পারে এমন ডুপ্লিকেট কোড চিনতে শুরু করবেন।

আমরা Listing 10-1-এর ছোট প্রোগ্রামটি দিয়ে শুরু করব যা একটি তালিকার বৃহত্তম সংখ্যা খুঁজে বের করে।

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 সবচেয়ে বড় সংখ্যাটিকে রেফার করবে, যা এই ক্ষেত্রে 100।

আমাদের এখন দুটি ভিন্ন সংখ্যার তালিকায় বৃহত্তম সংখ্যা খুঁজে বের করার দায়িত্ব দেওয়া হয়েছে। এটি করার জন্য, আমরা Listing 10-1-এর কোডটি ডুপ্লিকেট করতে পারি এবং প্রোগ্রামের দুটি ভিন্ন স্থানে একই লজিক ব্যবহার করতে পারি, যেমনটি Listing 10-2-তে দেখানো হয়েছে।

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}");
}

যদিও এই কোডটি কাজ করে, কোড ডুপ্লিকেট করা ক্লান্তিকর এবং এরর-প্রবণ। আমরা যখন কোড পরিবর্তন করতে চাই তখন একাধিক স্থানে কোড আপডেট করার কথাও মনে রাখতে হবে।

এই ডুপ্লিকেশন দূর করার জন্য, আমরা একটি অ্যাবস্ট্রাকশন (abstraction) তৈরি করব একটি ফাংশন সংজ্ঞায়িত করে যা প্যারামিটার হিসাবে পাস করা যেকোনো পূর্ণসংখ্যার তালিকার উপর কাজ করে। এই সমাধানটি আমাদের কোডকে আরও পরিষ্কার করে এবং আমাদের একটি তালিকা থেকে বৃহত্তম সংখ্যা খুঁজে বের করার ধারণাটিকে অ্যাবস্ট্রাক্টলি (abstractly) প্রকাশ করতে দেয়।

Listing 10-3-এ, আমরা বৃহত্তম সংখ্যা খুঁজে বের করার কোডটিকে largest নামক একটি ফাংশনে এক্সট্র্যাক্ট করি। তারপর আমরা Listing 10-2 থেকে দুটি তালিকায় বৃহত্তম সংখ্যা খুঁজে বের করার জন্য ফাংশনটিকে কল করি। আমরা ভবিষ্যতে আমাদের কাছে থাকতে পারে এমন 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 মানের কংক্রিট স্লাইসকে উপস্থাপন করে যা আমরা ফাংশনে পাস করতে পারি। ফলস্বরূপ, যখন আমরা ফাংশনটি কল করি, তখন কোডটি আমাদের পাস করা নির্দিষ্ট মানগুলোর উপর চলে।

সংক্ষেপে, এখানে আমরা Listing 10-2 থেকে Listing 10-3-এর কোড পরিবর্তন করার জন্য যে ধাপগুলো নিয়েছি সেগুলো হল:

  1. ডুপ্লিকেট কোড সনাক্ত করুন।
  2. ডুপ্লিকেট কোডটিকে ফাংশনের বডিতে এক্সট্র্যাক্ট করুন এবং ফাংশন সিগনেচারে সেই কোডের ইনপুট এবং রিটার্ন মানগুলো নির্দিষ্ট করুন।
  3. ডুপ্লিকেট কোডের দুটি ইন্সট্যান্স আপডেট করুন যাতে পরিবর্তে ফাংশনটিকে কল করা যায়।

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

উদাহরণস্বরূপ, ধরা যাক আমাদের দুটি ফাংশন ছিল: একটি যা i32 মানগুলোর একটি স্লাইসে বৃহত্তম আইটেম খুঁজে বের করে এবং একটি যা char মানগুলোর একটি স্লাইসে বৃহত্তম আইটেম খুঁজে বের করে। আমরা কীভাবে সেই ডুপ্লিকেশন দূর করব? চলুন খুঁজে বের করি!