জেনেরিক ডেটা টাইপ
আমরা ফাংশন সিগনেচার বা struct-এর মতো আইটেমগুলোর জন্য ডেফিনিশন তৈরি করতে generics ব্যবহার করি, যা আমরা পরে বিভিন্ন concrete ডেটা টাইপের সাথে ব্যবহার করতে পারি। চলুন প্রথমে দেখি কিভাবে generics ব্যবহার করে ফাংশন, struct, enum, এবং মেথড ডিফাইন করা যায়। তারপর আমরা আলোচনা করব generics কীভাবে কোডের পারফরম্যান্সকে প্রভাবিত করে।
ফাংশন ডেফিনিশনে
যখন আমরা generics ব্যবহার করে এমন একটি ফাংশন ডিফাইন করি, তখন আমরা ফাংশনের সিগনেচারে generics গুলোকে রাখি, যেখানে আমরা সাধারণত প্যারামিটার এবং রিটার্ন ভ্যালুর ডেটা টাইপ নির্দিষ্ট করি। এভাবে কোড লিখলে আমাদের কোড আরও বেশি ফ্লেক্সিবল হয়, কোডের পুনরাবৃত্তি রোধ করে এবং ফাংশন ব্যবহারকারীদের জন্য আরও বেশি কার্যকারিতা প্রদান করে।
আমাদের largest ফাংশনটি নিয়ে কাজ করা যাক। লিস্টিং ১০-৪ এ দুটি ফাংশন দেখানো হয়েছে যারা উভয়েই একটি স্লাইসের মধ্যে সবচেয়ে বড় মান খুঁজে বের করে। এরপর আমরা এদেরকে generics ব্যবহার করে একটি একক ফাংশনে একত্রিত করব।
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'); }``` </Listing> `largest_i32` ফাংশনটি আমরা লিস্টিং ১০-৩ এ এক্সট্র্যাক্ট করেছিলাম, যা একটি স্লাইস থেকে সবচেয়ে বড় `i32` খুঁজে বের করে। `largest_char` ফাংশনটি একটি স্লাইস থেকে সবচেয়ে বড় `char` খুঁজে বের করে। দুটি ফাংশনের বডি একই কোড ধারণ করে, তাই আসুন একটি জেনেরিক টাইপ প্যারামিটার ব্যবহার করে একটি একক ফাংশন তৈরি করে এই পুনরাবৃত্তি দূর করি। একটি নতুন একক ফাংশনে টাইপগুলোকে প্যারামিটারাইজ করার জন্য, আমাদের টাইপ প্যারামিটারের একটি নাম দিতে হবে, ঠিক যেমন আমরা একটি ফাংশনের ভ্যালু প্যারামিটারের জন্য নাম দিই। আপনি টাইপ প্যারামিটারের নাম হিসেবে যেকোনো আইডেন্টিফায়ার ব্যবহার করতে পারেন। কিন্তু আমরা `T` ব্যবহার করব কারণ, প্রথা অনুযায়ী, Rust-এ টাইপ প্যারামিটারের নাম ছোট হয়, প্রায়শই কেবল একটি অক্ষর, এবং Rust-এর টাইপ-নামকরণের প্রথা হলো CamelCase। _type_-এর সংক্ষিপ্ত রূপ হিসেবে `T` বেশিরভাগ Rust প্রোগ্রামারদের প্রথম পছন্দ। যখন আমরা ফাংশনের বডিতে একটি প্যারামিটার ব্যবহার করি, তখন আমাদের সিগনেচারে প্যারামিটারের নামটি ডিক্লেয়ার করতে হয় যাতে কম্পাইলার জানে সেই নামের অর্থ কী। একইভাবে, যখন আমরা একটি ফাংশন সিগনেচারে একটি টাইপ প্যারামিটারের নাম ব্যবহার করি, তখন ব্যবহারের আগে আমাদের টাইপ প্যারামিটারের নামটি ডিক্লেয়ার করতে হয়। জেনেরিক `largest` ফাংশনটি ডিফাইন করতে, আমরা ফাংশনের নাম এবং প্যারামিটার তালিকার মধ্যে অ্যাঙ্গেল ব্র্যাকেট `<>`-এর ভিতরে টাইপের নাম ডিক্লেয়ার করি, এভাবে: ```rust,ignore fn largest<T>(list: &[T]) -> &T {
এই ডেফিনিশনটিকে আমরা এভাবে পড়ি: largest ফাংশনটি কোনো একটি টাইপ T-এর উপর জেনেরিক। এই ফাংশনের list নামে একটি প্যারামিটার আছে, যা T টাইপের ভ্যালুগুলোর একটি স্লাইস। largest ফাংশনটি একই টাইপ T-এর একটি ভ্যালুর রেফারেন্স রিটার্ন করবে।
লিস্টিং ১০-৫ এ জেনেরিক ডেটা টাইপ ব্যবহার করে সম্মিলিত 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, এবং আমরা পরবর্তী সেকশনে traits নিয়ে কথা বলব। আপাতত, জেনে রাখুন যে এই এররটি বলছে যে largest ফাংশনের বডি T-এর সম্ভাব্য সকল টাইপের জন্য কাজ করবে না। যেহেতু আমরা বডিতে T টাইপের মান তুলনা করতে চাই, তাই আমরা কেবল সেই টাইপগুলো ব্যবহার করতে পারি যাদের মান வரிசை অনুযায়ী সাজানো (ordered) যায়। তুলনা সক্রিয় করার জন্য, স্ট্যান্ডার্ড লাইব্রেরিতে std::cmp::PartialOrd trait রয়েছে যা আপনি টাইপগুলিতে ইমপ্লিমেন্ট করতে পারেন (এই trait সম্পর্কে আরও জানতে Appendix C দেখুন)। লিস্টিং ১০-৫ ঠিক করার জন্য, আমরা হেল্প টেক্সট-এর পরামর্শ অনুসরণ করতে পারি এবং T-এর জন্য বৈধ টাইপগুলোকে কেবল তাদের মধ্যে সীমাবদ্ধ রাখতে পারি যারা PartialOrd ইমপ্লিমেন্ট করে। এরপর লিস্টিংটি কম্পাইল হবে, কারণ স্ট্যান্ডার্ড লাইব্রেরি i32 এবং char উভয়ের উপরেই PartialOrd ইমপ্লিমেন্ট করে।
Struct ডেফিনিশনে
আমরা <> সিনট্যাক্স ব্যবহার করে এক বা একাধিক ফিল্ডে জেনেরিক টাইপ প্যারামিটার ব্যবহার করার জন্য struct ডিফাইন করতে পারি। লিস্টিং ১০-৬ একটি Point<T> struct ডিফাইন করে যা যেকোনো টাইপের x এবং y কো-অর্ডিনেট ভ্যালু ধারণ করে।
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 }; }
struct ডেফিনিশনে generics ব্যবহারের সিনট্যাক্স ফাংশন ডেফিনিশনে ব্যবহারের মতোই। প্রথমে আমরা struct-এর নামের ঠিক পরে অ্যাঙ্গেল ব্র্যাকেটের মধ্যে টাইপ প্যারামিটারের নাম ডিক্লেয়ার করি। তারপর আমরা struct ডেফিনিশনের মধ্যে জেনেরিক টাইপ ব্যবহার করি যেখানে আমরা অন্যথায় concrete ডেটা টাইপ নির্দিষ্ট করতাম।
মনে রাখবেন যে আমরা Point<T> ডিফাইন করতে কেবল একটি জেনেরিক টাইপ ব্যবহার করেছি, তাই এই ডেফিনিশনটি বলে যে Point<T> struct-টি কোনো একটি টাইপ T-এর উপর জেনেরিক, এবং x ও y ফিল্ড দুটি উভয়ই সেই একই টাইপের, টাইপটি যা-ই হোক না কেন। যদি আমরা ভিন্ন টাইপের মান দিয়ে একটি Point<T>-এর ইনস্ট্যান্স তৈরি করি, যেমন লিস্টিং ১০-৭-এ, আমাদের কোড কম্পাইল হবে না।
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
x এবং y উভয়ই জেনেরিক কিন্তু ভিন্ন টাইপের হতে পারে এমন একটি Point struct ডিফাইন করতে, আমরা একাধিক জেনেরিক টাইপ প্যারামিটার ব্যবহার করতে পারি। উদাহরণস্বরূপ, লিস্টিং ১০-৮-এ, আমরা 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-এর সমস্ত ইনস্ট্যান্স অনুমোদিত! আপনি একটি ডেফিনিশনে যত খুশি জেনেরিক টাইপ প্যারামিটার ব্যবহার করতে পারেন, তবে অল্প কয়েকটির বেশি ব্যবহার করলে আপনার কোড পড়া কঠিন হয়ে যায়। যদি আপনার কোডে অনেক জেনেরিক টাইপের প্রয়োজন হয়, তবে এটি ইঙ্গিত দিতে পারে যে আপনার কোডকে ছোট ছোট অংশে পুনর্গঠন করা প্রয়োজন।
Enum ডেফিনিশনে
যেমনটি আমরা struct-এর সাথে করেছি, আমরা enum-কেও তাদের ভ্যারিয়েন্টে জেনেরিক ডেটা টাইপ ধারণ করার জন্য ডিফাইন করতে পারি। আসুন আমরা স্ট্যান্ডার্ড লাইব্রেরির দেওয়া Option<T> enum-টি আবার দেখি, যা আমরা চ্যাপ্টার ৬-এ ব্যবহার করেছি:
#![allow(unused)] fn main() { enum Option<T> { Some(T), None, } }
এই ডেফিনিশনটি এখন আপনার কাছে আরও বেশি অর্থবহ মনে হওয়া উচিত। जैसा कि आप देख सकते हैं, Option<T> enum টি T টাইপের উপর জেনেরিক এবং এর দুটি ভ্যারিয়েন্ট রয়েছে: Some, যা T টাইপের একটি মান ধারণ করে, এবং None ভ্যারিয়েন্ট যা কোনো মান ধারণ করে না। Option<T> enum ব্যবহার করে, আমরা একটি ঐচ্ছিক মানের অ্যাবস্ট্রাক্ট ধারণা প্রকাশ করতে পারি, এবং যেহেতু Option<T> জেনেরিক, তাই ঐচ্ছিক মানের টাইপ যা-ই হোক না কেন, আমরা এই অ্যাবস্ট্রাকশনটি ব্যবহার করতে পারি।
Enum একাধিক জেনেরিক টাইপও ব্যবহার করতে পারে। Result enum-এর ডেফিনিশন, যা আমরা চ্যাপ্টার ৯-এ ব্যবহার করেছি, এর একটি উদাহরণ:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
Result enum-টি T এবং E দুটি টাইপের উপর জেনেরিক, এবং এর দুটি ভ্যারিয়েন্ট রয়েছে: Ok, যা T টাইপের একটি মান ধারণ করে, এবং Err, যা E টাইপের একটি মান ধারণ করে। এই ডেফিনিশনটি Result enum ব্যবহার করা সুবিধাজনক করে তোলে যেখানেই আমাদের এমন কোনো অপারেশন থাকে যা সফল হতে পারে (T টাইপের কোনো মান রিটার্ন করে) বা ব্যর্থ হতে পারে (E টাইপের কোনো এরর রিটার্ন করে)। প্রকৃতপক্ষে, এটিই আমরা লিস্টিং ৯-৩-এ একটি ফাইল খোলার জন্য ব্যবহার করেছিলাম, যেখানে ফাইলটি সফলভাবে খোলা হলে T-কে std::fs::File টাইপ দিয়ে পূরণ করা হয়েছিল এবং ফাইল খুলতে সমস্যা হলে E-কে std::io::Error টাইপ দিয়ে পূরণ করা হয়েছিল।
যখন আপনি আপনার কোডে এমন পরিস্থিতি শনাক্ত করেন যেখানে একাধিক struct বা enum ডেফিনিশন রয়েছে যা কেবল তাদের ধারণ করা মানের টাইপের দিক থেকে ভিন্ন, তখন আপনি জেনেরিক টাইপ ব্যবহার করে পুনরাবৃত্তি এড়াতে পারেন।
মেথড ডেফিনিশনে
আমরা struct এবং enum-এর উপর মেথড ইমপ্লিমেন্ট করতে পারি (যেমনটি আমরা চ্যাপ্টার ৫-এ করেছি) এবং তাদের ডেফিনিশনেও জেনেরিক টাইপ ব্যবহার করতে পারি। লিস্টিং ১০-৯-এ আমরা লিস্টিং ১০-৬-এ ডিফাইন করা Point<T> struct-টি দেখাচ্ছি, যার উপর 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-এর অ্যাঙ্গেল ব্র্যাকেটের মধ্যে থাকা টাইপটি একটি জেনেরিক টাইপ, কোনো concrete টাইপ নয়। আমরা struct ডেফিনিশনে ডিক্লেয়ার করা জেনেরিক প্যারামিটারের চেয়ে এই জেনেরিক প্যারামিটারের জন্য একটি ভিন্ন নাম বেছে নিতে পারতাম, কিন্তু একই নাম ব্যবহার করাই প্রচলিত। যদি আপনি একটি জেনেরিক টাইপ ডিক্লেয়ার করে এমন একটি impl-এর মধ্যে একটি মেথড লেখেন, তবে সেই মেথডটি টাইপের যেকোনো ইনস্ট্যান্সের উপর ডিফাইন করা হবে, জেনেরিক টাইপের পরিবর্তে যে কোনো concrete টাইপই আসুক না কেন।
আমরা টাইপের উপর মেথড ডিফাইন করার সময় জেনেরিক টাইপের উপর সীমাবদ্ধতাও নির্দিষ্ট করতে পারি। উদাহরণস্বরূপ, আমরা যেকোনো জেনেরিক টাইপের Point<T> ইনস্ট্যান্সের পরিবর্তে শুধুমাত্র Point<f32> ইনস্ট্যান্সের উপর মেথড ইমপ্লিমেন্ট করতে পারি। লিস্টিং ১০-১০-এ আমরা concrete টাইপ 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) কো-অর্ডিনেটের পয়েন্ট থেকে কত দূরে তা পরিমাপ করে এবং গাণিতিক অপারেশন ব্যবহার করে যা শুধুমাত্র ফ্লোটিং-পয়েন্ট টাইপের জন্য উপলব্ধ।
একটি struct ডেফিনিশনের জেনেরিক টাইপ প্যারামিটার সবসময় সেই একই struct-এর মেথড সিগনেচারে ব্যবহার করা প্যারামিটারের মতো হয় না। লিস্টিং ১০-১১ উদাহরণটিকে আরও স্পষ্ট করার জন্য Point struct-এর জন্য X1 এবং Y1 এবং mixup মেথড সিগনেচারের জন্য X2 Y2 জেনেরিক টাইপ ব্যবহার করে। মেথডটি self Point (যার টাইপ X1) থেকে x ভ্যালু এবং পাস করা Point (যার টাইপ Y2) থেকে y ভ্যালু নিয়ে একটি নতুন 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 struct যার 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-এর পরে ডিক্লেয়ার করা হয়েছে কারণ তারা struct ডেফিনিশনের সাথে যায়। জেনেরিক প্যারামিটার X2 এবং Y2 fn mixup-এর পরে ডিক্লেয়ার করা হয়েছে কারণ তারা কেবল মেথডের জন্য প্রাসঙ্গিক।
Generics ব্যবহার করা কোডের পারফরম্যান্স
আপনি হয়তো ভাবছেন যে জেনেরিক টাইপ প্যারামিটার ব্যবহার করার সময় কোনো রানটাইম খরচ আছে কিনা। সুখবর হলো যে জেনেরিক টাইপ ব্যবহার করলে আপনার প্রোগ্রামটি concrete টাইপ ব্যবহার করার চেয়ে কোনো ধীর গতিতে চলবে না।
Rust কম্পাইল টাইমে generics ব্যবহার করা কোডের মনোমর্ফাইজেশন (monomorphization) সম্পাদন করে এটি অর্জন করে। Monomorphization হলো কম্পাইল টাইমে ব্যবহৃত concrete টাইপগুলো দিয়ে জেনেরিক কোডকে নির্দিষ্ট কোডে পরিণত করার প্রক্রিয়া। এই প্রক্রিয়ায়, কম্পাইলার আমরা লিস্টিং ১০-৫-এ জেনেরিক ফাংশন তৈরি করার জন্য যে পদক্ষেপগুলো ব্যবহার করেছি তার বিপরীত কাজ করে: কম্পাইলার সেই সমস্ত জায়গা দেখে যেখানে জেনেরিক কোড কল করা হয়েছে এবং যে concrete টাইপ দিয়ে জেনেরিক কোড কল করা হয়েছে তার জন্য কোড তৈরি করে।
আসুন দেখি এটি কীভাবে কাজ করে স্ট্যান্ডার্ড লাইব্রেরির জেনেরিক Option<T> enum ব্যবহার করে:
#![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 জেনেরিক কোডকে এমন কোডে কম্পাইল করে যা প্রতিটি ইনস্ট্যান্সে টাইপ নির্দিষ্ট করে, তাই generics ব্যবহারের জন্য আমাদের কোনো রানটাইম খরচ দিতে হয় না। কোডটি যখন চলে, তখন এটি ঠিক তেমনই পারফর্ম করে যেমনটি আমরা প্রতিটি ডেফিনিশন হাতে হাতে ডুপ্লিকেট করলে করত। মনোমর্ফাইজেশন প্রক্রিয়াটি রানটাইমে Rust-এর generics-কে অত্যন্ত কার্যকরী করে তোলে।