জেনেরিক ডেটা টাইপ (Generic Data Types)

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

ফাংশন সংজ্ঞায় (In Function Definitions)

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

আমাদের largest ফাংশনটি নিয়ে আলোচনা চালিয়ে গেলে, Listing 10-4 দুটি ফাংশন দেখায় যা উভয়ই একটি স্লাইসের বৃহত্তম মান খুঁজে বের করে। আমরা তারপর সেগুলোকে জেনেরিক ব্যবহার করে একটি একক ফাংশনে একত্রিত করব।

fn largest_i32(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> &char {
    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_i32(&number_list);
    println!("The largest number is {result}");
    assert_eq!(*result, 100);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest_char(&char_list);
    println!("The largest char is {result}");
    assert_eq!(*result, 'y');
}

largest_i32 ফাংশনটি হল সেই ফাংশন যা আমরা Listing 10-3-এ এক্সট্র্যাক্ট করেছি, যেটি একটি স্লাইসের বৃহত্তম i32 খুঁজে বের করে। largest_char ফাংশনটি একটি স্লাইসের বৃহত্তম char খুঁজে বের করে। ফাংশন বডিগুলোর একই কোড রয়েছে, তাই আসুন একটি একক ফাংশনে জেনেরিক টাইপ প্যারামিটার প্রবর্তন করে ডুপ্লিকেশন দূর করি।

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

যখন আমরা ফাংশনের বডিতে একটি প্যারামিটার ব্যবহার করি, তখন আমাদের সিগনেচারে প্যারামিটারের নাম ঘোষণা করতে হবে যাতে কম্পাইলার বুঝতে পারে সেই নামের অর্থ কী। একইভাবে, যখন আমরা একটি ফাংশন সিগনেচারে একটি টাইপ প্যারামিটারের নাম ব্যবহার করি, তখন আমাদের এটি ব্যবহার করার আগে টাইপ প্যারামিটারের নাম ঘোষণা করতে হবে। জেনেরিক largest ফাংশন সংজ্ঞায়িত করতে, আমরা ফাংশনের নাম এবং প্যারামিটার তালিকার মধ্যে অ্যাঙ্গেল ব্র্যাকেট, <>,-এর ভিতরে টাইপের নামের ঘোষণাগুলো রাখি, এইভাবে:

fn largest<T>(list: &[T]) -> &T {

আমরা এই সংজ্ঞাটি এভাবে পড়ি: ফাংশন largest কিছু টাইপ T-এর উপর জেনেরিক। এই ফাংশনটির একটি প্যারামিটার রয়েছে যার নাম list, যেটি টাইপ T-এর মানগুলোর একটি স্লাইস। largest ফাংশনটি একই টাইপ T-এর একটি মানের রেফারেন্স রিটার্ন করবে।

Listing 10-5 তার সিগনেচারে জেনেরিক ডেটা টাইপ ব্যবহার করে সম্মিলিত largest ফাংশন সংজ্ঞা দেখায়। লিস্টিংটি আরও দেখায় কিভাবে আমরা ফাংশনটিকে i32 মান বা char মানগুলোর স্লাইস দিয়ে কল করতে পারি। মনে রাখবেন যে এই কোডটি এখনও কম্পাইল হবে না, কিন্তু আমরা এই চ্যাপ্টারের পরে এটি ঠিক করব।

fn largest<T>(list: &[T]) -> &T {
    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}");

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {result}");
}

যদি আমরা এখনই এই কোডটি কম্পাইল করি, তাহলে আমরা এই এররটি পাব:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `&T`
 --> src/main.rs:5:17
  |
5 |         if item > largest {
  |            ---- ^ ------- &T
  |            |
  |            &T
  |
help: consider restricting type parameter `T` with trait `PartialOrd`
  |
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
  |             ++++++++++++++++++++++

For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

হেল্প টেক্সট std::cmp::PartialOrd উল্লেখ করে, যেটি একটি ট্রেইট (trait), এবং আমরা পরবর্তী বিভাগে ট্রেইট নিয়ে কথা বলব। আপাতত, জেনে রাখুন যে এই এররটি বলে যে largest-এর বডি T-এর সম্ভাব্য সমস্ত টাইপের জন্য কাজ করবে না। যেহেতু আমরা বডিতে T টাইপের মানগুলোর তুলনা করতে চাই, তাই আমরা শুধুমাত্র সেই টাইপগুলো ব্যবহার করতে পারি যাদের মানগুলো অর্ডার করা যায়। তুলনা সক্রিয় করতে, স্ট্যান্ডার্ড লাইব্রেরিতে std::cmp::PartialOrd ট্রেইট রয়েছে যা আপনি টাইপগুলোতে ইমপ্লিমেন্ট করতে পারেন (এই ট্রেইট সম্পর্কে আরও জানতে Appendix C দেখুন)। হেল্প টেক্সটের পরামর্শ অনুসরণ করে, আমরা T-এর জন্য বৈধ টাইপগুলোকে শুধুমাত্র তাদের মধ্যে সীমাবদ্ধ করি যারা PartialOrd ইমপ্লিমেন্ট করে এবং এই উদাহরণটি কম্পাইল হবে, কারণ স্ট্যান্ডার্ড লাইব্রেরি i32 এবং char উভয়টিতেই PartialOrd ইমপ্লিমেন্ট করে।

স্ট্রাকট সংজ্ঞায় (In Struct Definitions)

আমরা <> সিনট্যাক্স ব্যবহার করে এক বা একাধিক ফিল্ডে জেনেরিক টাইপ প্যারামিটার ব্যবহার করে স্ট্রাকট সংজ্ঞায়িত করতে পারি। Listing 10-6 যেকোনো টাইপের x এবং y কো-অর্ডিনেট মান ধারণ করার জন্য একটি Point<T> স্ট্রাকট সংজ্ঞায়িত করে।

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

স্ট্রাকট সংজ্ঞায় জেনেরিক ব্যবহারের সিনট্যাক্স ফাংশন সংজ্ঞায় ব্যবহৃত সিনট্যাক্সের অনুরূপ। প্রথমে আমরা স্ট্রাকটের নামের ঠিক পরেই অ্যাঙ্গেল ব্র্যাকেটের ভিতরে টাইপ প্যারামিটারের নাম ঘোষণা করি। তারপর আমরা স্ট্রাকট সংজ্ঞায় জেনেরিক টাইপ ব্যবহার করি যেখানে আমরা অন্যথায় কংক্রিট ডেটা টাইপ নির্দিষ্ট করতাম।

লক্ষ্য করুন যে যেহেতু আমরা Point<T> সংজ্ঞায়িত করার জন্য শুধুমাত্র একটি জেনেরিক টাইপ ব্যবহার করেছি, এই সংজ্ঞাটি বলে যে Point<T> স্ট্রাকটটি কিছু টাইপ T-এর উপর জেনেরিক, এবং x এবং y ফিল্ডগুলো উভয়ই একই টাইপের, সেই টাইপ যাই হোক না কেন। আমরা যদি Point<T>-এর একটি ইন্সট্যান্স তৈরি করি যেখানে বিভিন্ন টাইপের মান রয়েছে, যেমনটি Listing 10-7-এ রয়েছে, তাহলে আমাদের কোড কম্পাইল হবে না।

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let wont_work = Point { x: 5, y: 4.0 };
}

এই উদাহরণে, যখন আমরা x-এ ইন্টিজার মান 5 অ্যাসাইন করি, তখন আমরা কম্পাইলারকে জানাই যে এই Point<T> ইন্সট্যান্সের জন্য জেনেরিক টাইপ T একটি ইন্টিজার হবে। তারপর যখন আমরা y-এর জন্য 4.0 নির্দিষ্ট করি, যাকে আমরা x-এর মতোই একই টাইপ হিসাবে সংজ্ঞায়িত করেছি, তখন আমরা এইরকম একটি টাইপ মিসম্যাচ এরর পাব:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0308]: mismatched types
 --> src/main.rs:7:38
  |
7 |     let wont_work = Point { x: 5, y: 4.0 };
  |                                      ^^^ expected integer, found floating-point number

For more information about this error, try `rustc --explain E0308`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

একটি Point স্ট্রাকট সংজ্ঞায়িত করতে যেখানে x এবং y উভয়ই জেনেরিক কিন্তু ভিন্ন টাইপ হতে পারে, আমরা একাধিক জেনেরিক টাইপ প্যারামিটার ব্যবহার করতে পারি। উদাহরণস্বরূপ, Listing 10-8-এ, আমরা Point-এর সংজ্ঞা পরিবর্তন করে T এবং U টাইপের উপর জেনেরিক করি যেখানে x হল T টাইপের এবং y হল U টাইপের।

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

এখন দেখানো Point-এর সমস্ত ইন্সট্যান্স অনুমোদিত! আপনি একটি সংজ্ঞায় যত খুশি জেনেরিক টাইপ প্যারামিটার ব্যবহার করতে পারেন, কিন্তু কয়েকটির বেশি ব্যবহার করলে আপনার কোড পড়া কঠিন হয়ে যায়। আপনি যদি আপনার কোডে প্রচুর জেনেরিক টাইপের প্রয়োজন বোধ করেন, তাহলে এটি ইঙ্গিত করতে পারে যে আপনার কোডটিকে ছোট ছোট অংশে পুনর্গঠন করা দরকার।

এনাম সংজ্ঞায় (In Enum Definitions)

আমরা যেমন স্ট্রাকট দিয়ে করেছি, তেমনি আমরা এনাম সংজ্ঞায়িত করতে পারি যাতে তাদের ভেরিয়েন্টগুলোতে জেনেরিক ডেটা টাইপ থাকে। আসুন স্ট্যান্ডার্ড লাইব্রেরি দ্বারা সরবরাহ করা Option<T> এনামটি আবার দেখি, যা আমরা চ্যাপ্টার ৬-এ ব্যবহার করেছি:

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

এই সংজ্ঞাটি এখন আপনার কাছে আরও বোধগম্য হওয়া উচিত। আপনি যেমন দেখতে পাচ্ছেন, Option<T> এনামটি T টাইপের উপর জেনেরিক এবং এর দুটি ভেরিয়েন্ট রয়েছে: Some, যা T টাইপের একটি মান ধারণ করে এবং একটি None ভেরিয়েন্ট যা কোনো মান ধারণ করে না। Option<T> এনাম ব্যবহার করে, আমরা একটি ঐচ্ছিক মানের অ্যাবস্ট্রাক্ট ধারণা প্রকাশ করতে পারি এবং যেহেতু Option<T> জেনেরিক, তাই আমরা এই অ্যাবস্ট্রাকশনটি ব্যবহার করতে পারি ঐচ্ছিক মানের টাইপ যাই হোক না কেন।

এনামগুলো একাধিক জেনেরিক টাইপও ব্যবহার করতে পারে। Result এনামের সংজ্ঞা যা আমরা চ্যাপ্টার ৯-এ ব্যবহার করেছি তার একটি উদাহরণ:

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

Result এনামটি দুটি টাইপ, T এবং E-এর উপর জেনেরিক এবং এর দুটি ভেরিয়েন্ট রয়েছে: Ok, যা T টাইপের একটি মান ধারণ করে এবং Err, যা E টাইপের একটি মান ধারণ করে। এই সংজ্ঞাটি Result এনামটিকে যেকোনো জায়গায় ব্যবহার করা সুবিধাজনক করে তোলে যেখানে আমাদের এমন একটি অপারেশন রয়েছে যা সফল হতে পারে (কিছু টাইপ T-এর একটি মান রিটার্ন করে) বা ব্যর্থ হতে পারে (কিছু টাইপ E-এর একটি এরর রিটার্ন করে)। প্রকৃতপক্ষে, এটিই আমরা Listing 9-3-তে একটি ফাইল খুলতে ব্যবহার করেছি, যেখানে ফাইলটি সফলভাবে খোলা হলে T std::fs::File টাইপ দিয়ে পূরণ করা হয়েছিল এবং ফাইলটি খোলার ক্ষেত্রে সমস্যা হলে E std::io::Error টাইপ দিয়ে পূরণ করা হয়েছিল।

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

মেথড সংজ্ঞায় (In Method Definitions)

আমরা স্ট্রাকট এবং এনামগুলোতে মেথড ইমপ্লিমেন্ট করতে পারি (যেমনটি আমরা চ্যাপ্টার ৫-এ করেছি) এবং তাদের সংজ্ঞায় জেনেরিক টাইপও ব্যবহার করতে পারি। Listing 10-9 Listing 10-6-এ সংজ্ঞায়িত Point<T> স্ট্রাকটটি দেখায় যার উপর x নামক একটি মেথড ইমপ্লিমেন্ট করা হয়েছে।

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

এখানে, আমরা Point<T>-তে x নামে একটি মেথড সংজ্ঞায়িত করেছি যা x ফিল্ডের ডেটার একটি রেফারেন্স রিটার্ন করে।

লক্ষ্য করুন যে আমাদের impl-এর পরেই T ঘোষণা করতে হবে যাতে আমরা Point<T> টাইপে মেথড ইমপ্লিমেন্ট করছি তা নির্দিষ্ট করতে T ব্যবহার করতে পারি। impl-এর পরে T-কে জেনেরিক টাইপ হিসাবে ঘোষণা করে, Rust শনাক্ত করতে পারে যে Point-এর অ্যাঙ্গেল ব্র্যাকেটের টাইপটি একটি কংক্রিট টাইপের পরিবর্তে একটি জেনেরিক টাইপ। আমরা স্ট্রাকট সংজ্ঞায় ঘোষিত জেনেরিক প্যারামিটারের চেয়ে এই জেনেরিক প্যারামিটারের জন্য একটি ভিন্ন নাম বেছে নিতে পারতাম, কিন্তু একই নাম ব্যবহার করা প্রচলিত। একটি impl-এর মধ্যে লেখা মেথড যা জেনেরিক টাইপ ঘোষণা করে, সেটি টাইপের যেকোনো ইন্সট্যান্সে সংজ্ঞায়িত করা হবে, জেনেরিক টাইপের পরিবর্তে শেষ পর্যন্ত কোন কংক্রিট টাইপ ব্যবহার করা হোক না কেন।

আমরা টাইপের উপর মেথড সংজ্ঞায়িত করার সময় জেনেরিক টাইপগুলোতে সীমাবদ্ধতাও নির্দিষ্ট করতে পারি। উদাহরণস্বরূপ, আমরা যেকোনো জেনেরিক টাইপ সহ Point<T> ইন্সট্যান্সের পরিবর্তে শুধুমাত্র Point<f32> ইন্সট্যান্সগুলোতে মেথড ইমপ্লিমেন্ট করতে পারি। Listing 10-10-এ আমরা কংক্রিট টাইপ f32 ব্যবহার করি, যার অর্থ আমরা impl-এর পরে কোনো টাইপ ঘোষণা করি না।

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

এই কোডটির অর্থ হল Point<f32> টাইপের একটি distance_from_origin মেথড থাকবে; Point<T>-এর অন্যান্য ইন্সট্যান্স যেখানে T টাইপ f32 নয়, তাদের এই মেথডটি সংজ্ঞায়িত করা হবে না। মেথডটি পরিমাপ করে যে আমাদের পয়েন্টটি (0.0, 0.0) কো-অর্ডিনেটের পয়েন্ট থেকে কত দূরে এবং গাণিতিক অপারেশন ব্যবহার করে যা শুধুমাত্র ফ্লোটিং-পয়েন্ট টাইপের জন্য উপলব্ধ।

একটি স্ট্রাকট সংজ্ঞায় জেনেরিক টাইপ প্যারামিটারগুলো সর্বদাই সেই একই স্ট্রাকটের মেথড সিগনেচারে আপনি যেগুলো ব্যবহার করেন সেগুলো নয়। Listing 10-11 উদাহরণটিকে আরও স্পষ্ট করার জন্য Point স্ট্রাকটের জন্য জেনেরিক টাইপ X1 এবং Y1 এবং mixup মেথড সিগনেচারের জন্য X2 Y2 ব্যবহার করে। মেথডটি self Point-এর x মান (টাইপ X1) এবং পাস করা Point-এর y মান (টাইপ Y2) দিয়ে একটি নতুন Point ইন্সট্যান্স তৈরি করে।

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

main-এ, আমরা একটি Point সংজ্ঞায়িত করেছি যার x-এর জন্য একটি i32 (মান 5 সহ) এবং y-এর জন্য একটি f64 (মান 10.4 সহ) রয়েছে। p2 ভেরিয়েবল হল একটি Point স্ট্রাকট যার x-এর জন্য একটি স্ট্রিং স্লাইস (মান "Hello" সহ) এবং y-এর জন্য একটি char (মান c সহ) রয়েছে। p1-এ p2 আর্গুমেন্ট সহ mixup কল করা আমাদের p3 দেয়, যার x-এর জন্য একটি i32 থাকবে, কারণ x, p1 থেকে এসেছে। p3 ভেরিয়েবলের y-এর জন্য একটি char থাকবে, কারণ y, p2 থেকে এসেছে। println! ম্যাক্রো কলটি p3.x = 5, p3.y = c প্রিন্ট করবে।

এই উদাহরণের উদ্দেশ্য হল এমন একটি পরিস্থিতি প্রদর্শন করা যেখানে কিছু জেনেরিক প্যারামিটার impl দিয়ে ঘোষণা করা হয় এবং কিছু মেথড সংজ্ঞা দিয়ে ঘোষণা করা হয়। এখানে, জেনেরিক প্যারামিটার X1 এবং Y1 impl-এর পরে ঘোষণা করা হয়েছে কারণ সেগুলো স্ট্রাকট সংজ্ঞার সাথে সম্পর্কিত। জেনেরিক প্যারামিটার X2 এবং Y2 fn mixup-এর পরে ঘোষণা করা হয়েছে কারণ সেগুলো শুধুমাত্র মেথডের সাথে সম্পর্কিত।

জেনেরিক ব্যবহার করা কোডের পারফরম্যান্স (Performance of Code Using Generics)

আপনি হয়তো ভাবছেন যে জেনেরিক টাইপ প্যারামিটার ব্যবহার করার সময় কোনো রানটাইম খরচ আছে কিনা। ভাল খবর হল যে জেনেরিক টাইপ ব্যবহার করা আপনার প্রোগ্রামকে কংক্রিট টাইপ ব্যবহার করার চেয়ে কোনোভাবেই ধীর করবে না।

Rust কম্পাইল করার সময় জেনেরিক ব্যবহার করে কোডের মনোমরফাইজেশন (monomorphization) সম্পাদন করে এটি সম্পন্ন করে। মনোমর্ফাইজেশন হল জেনেরিক কোডকে কংক্রিট টাইপ দিয়ে পূরণ করে নির্দিষ্ট কোডে পরিণত করার প্রক্রিয়া, যেগুলো কম্পাইল করার সময় ব্যবহার করা হয়। এই প্রক্রিয়ায়, কম্পাইলার Listing 10-5-এ জেনেরিক ফাংশন তৈরি করতে আমরা যে ধাপগুলো ব্যবহার করেছি তার বিপরীত কাজ করে: কম্পাইলার সেই সমস্ত জায়গাগুলো দেখে যেখানে জেনেরিক কোড কল করা হয়েছে এবং জেনেরিক কোডটি যে কংক্রিট টাইপগুলোর সাথে কল করা হয়েছে তার জন্য কোড জেনারেট করে।

আসুন দেখি কিভাবে এটি স্ট্যান্ডার্ড লাইব্রেরির জেনেরিক Option<T> এনাম ব্যবহার করে কাজ করে:

#![allow(unused)]
fn main() {
let integer = Some(5);
let float = Some(5.0);
}

যখন Rust এই কোডটি কম্পাইল করে, তখন এটি মনোমরফাইজেশন সম্পাদন করে। সেই প্রক্রিয়া চলাকালীন, কম্পাইলার Option<T> ইন্সট্যান্সে ব্যবহৃত মানগুলো পড়ে এবং দুই ধরনের Option<T> শনাক্ত করে: একটি হল i32 এবং অন্যটি হল f64। সেই অনুযায়ী, এটি Option<T>-এর জেনেরিক সংজ্ঞাকে i32 এবং f64-এর জন্য বিশেষায়িত দুটি সংজ্ঞায় প্রসারিত করে, এইভাবে জেনেরিক সংজ্ঞাকে নির্দিষ্ট সংজ্ঞা দিয়ে প্রতিস্থাপন করে।

কোডের মনোমরফাইজড ভার্সনটি দেখতে নিচের মতো (কম্পাইলার এখানে উদাহরণের জন্য আমরা যা ব্যবহার করছি তার চেয়ে ভিন্ন নাম ব্যবহার করে):

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

জেনেরিক Option<T> কম্পাইলার দ্বারা তৈরি করা নির্দিষ্ট সংজ্ঞা দিয়ে প্রতিস্থাপিত হয়। যেহেতু Rust জেনেরিক কোডকে এমন কোডে কম্পাইল করে যা প্রতিটি ইন্সট্যান্সে টাইপ নির্দিষ্ট করে, তাই আমরা জেনেরিক ব্যবহারের জন্য কোনো রানটাইম খরচ দিই না। যখন কোডটি চলে, তখন এটি এমনভাবে কাজ করে যেন আমরা প্রতিটি সংজ্ঞা হাতে ডুপ্লিকেট করেছি। মনোমরফাইজেশনের প্রক্রিয়া Rust-এর জেনেরিকগুলোকে রানটাইমে অত্যন্ত কার্যকরী করে তোলে।