ট্রেইট (Traits): সাধারণ আচরণ ডিফাইন করা
একটি trait কোনো নির্দিষ্ট টাইপের কার্যকারিতা (functionality) ডিফাইন করে যা অন্যান্য টাইপের সাথে শেয়ার করা যায়। আমরা অ্যাবস্ট্রাক্ট উপায়ে সাধারণ আচরণ (shared behavior) ডিফাইন করতে traits ব্যবহার করতে পারি। আমরা trait bounds ব্যবহার করে নির্দিষ্ট করতে পারি যে একটি জেনেরিক টাইপ যেকোনো টাইপের হতে পারে, যতক্ষণ পর্যন্ত তার একটি নির্দিষ্ট আচরণ থাকে।
দ্রষ্টব্য: Traits অন্যান্য ভাষার interfaces নামক একটি ফিচারের মতো, যদিও কিছু পার্থক্য রয়েছে।
একটি ট্রেইট ডিফাইন করা
একটি টাইপের আচরণ হলো সেইসব মেথড যা আমরা সেই টাইপের উপর কল করতে পারি। বিভিন্ন টাইপ একই আচরণ শেয়ার করে যদি আমরা সেই সব টাইপের উপর একই মেথড কল করতে পারি। Trait ডেফিনিশন হলো মেথড সিগনেচারগুলোকে একসাথে গ্রুপ করার একটি উপায়, যা কোনো উদ্দেশ্য পূরণের জন্য প্রয়োজনীয় আচরণের একটি সেট ডিফাইন করে।
উদাহরণস্বরূপ, ধরা যাক আমাদের একাধিক struct আছে যা বিভিন্ন ধরনের এবং পরিমাণের টেক্সট ধারণ করে: একটি NewsArticle
struct যা একটি নির্দিষ্ট অবস্থানে ফাইল করা সংবাদ ধরে রাখে এবং একটি SocialPost
যা সর্বোচ্চ ২৮০ অক্ষর ধারণ করতে পারে, সাথে মেটাডেটা যা নির্দেশ করে এটি একটি নতুন পোস্ট, একটি রিপোস্ট, বা অন্য কোনো পোস্টের উত্তর ছিল কিনা।
আমরা aggregator
নামে একটি মিডিয়া অ্যাগ্রিগেটর লাইব্রেরি ক্রেট তৈরি করতে চাই যা NewsArticle
বা SocialPost
ইনস্ট্যান্সে সংরক্ষিত ডেটার সারাংশ প্রদর্শন করতে পারে। এটি করার জন্য, আমাদের প্রতিটি টাইপ থেকে একটি সারাংশ প্রয়োজন, এবং আমরা একটি ইনস্ট্যান্সের উপর summarize
মেথড কল করে সেই সারাংশটি অনুরোধ করব। লিস্টিং ১০-১২ একটি পাবলিক Summary
trait-এর ডেফিনিশন দেখায় যা এই আচরণটি প্রকাশ করে।
pub trait Summary {
fn summarize(&self) -> String;
}
এখানে, আমরা trait
কিওয়ার্ড ব্যবহার করে একটি trait ডিক্লেয়ার করি এবং তারপর trait-এর নাম, যা এই ক্ষেত্রে Summary
। আমরা trait-টিকে pub
হিসেবেও ডিক্লেয়ার করি যাতে এই ক্রেটের উপর নির্ভরশীল ক্রেটগুলোও এই trait ব্যবহার করতে পারে, যেমনটি আমরা কয়েকটি উদাহরণে দেখব। কার্লি ব্র্যাকেটের ভিতরে, আমরা মেথড সিগনেচারগুলো ডিক্লেয়ার করি যা এই trait ইমপ্লিমেন্ট করা টাইপগুলোর আচরণ বর্ণনা করে, যা এই ক্ষেত্রে fn summarize(&self) -> String
।
মেথড সিগনেচারের পরে, কার্লি ব্র্যাকেটের মধ্যে একটি ইমপ্লিমেন্টেশন প্রদান করার পরিবর্তে, আমরা একটি সেমিকোলন ব্যবহার করি। এই trait ইমপ্লিমেন্ট করা প্রতিটি টাইপকে অবশ্যই মেথডের বডির জন্য নিজস্ব কাস্টম আচরণ প্রদান করতে হবে। কম্পাইলার নিশ্চিত করবে যে Summary
trait যুক্ত যেকোনো টাইপের summarize
মেথডটি ঠিক এই সিগনেচার দিয়ে ডিফাইন করা থাকবে।
একটি trait-এর বডিতে একাধিক মেথড থাকতে পারে: মেথড সিগনেচারগুলো প্রতি লাইনে একটি করে তালিকাভুক্ত করা হয় এবং প্রতিটি লাইন একটি সেমিকোলন দিয়ে শেষ হয়।
একটি টাইপের উপর ট্রেইট ইমপ্লিমেন্ট করা
এখন যেহেতু আমরা Summary
trait-এর মেথডগুলোর কাঙ্ক্ষিত সিগনেচার ডিফাইন করেছি, আমরা এটিকে আমাদের মিডিয়া অ্যাগ্রিগেটরের টাইপগুলোতে ইমপ্লিমেন্ট করতে পারি। লিস্টিং ১০-১৩ NewsArticle
struct-এর উপর Summary
trait-এর একটি ইমপ্লিমেন্টেশন দেখায় যা summarize
-এর রিটার্ন ভ্যালু তৈরি করতে হেডলাইন, লেখক এবং অবস্থান ব্যবহার করে। SocialPost
struct-এর জন্য, আমরা summarize
-কে ইউজারের নাম এবং পোস্টের সম্পূর্ণ টেক্সট হিসেবে ডিফাইন করি, এই ধরে নিয়ে যে পোস্টের বিষয়বস্তু ইতিমধ্যে ২৮০ অক্ষরের মধ্যে সীমাবদ্ধ।
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
একটি টাইপের উপর একটি trait ইমপ্লিমেন্ট করা সাধারণ মেথড ইমপ্লিমেন্ট করার মতোই। পার্থক্য হলো impl
-এর পরে, আমরা যে trait-টি ইমপ্লিমেন্ট করতে চাই তার নাম লিখি, তারপর for
কিওয়ার্ড ব্যবহার করি এবং তারপর যে টাইপের জন্য trait-টি ইমপ্লিমেন্ট করতে চাই তার নাম নির্দিষ্ট করি। impl
ব্লকের মধ্যে, আমরা trait ডেফিনিশনে সংজ্ঞায়িত মেথড সিগনেচারগুলো রাখি। প্রতিটি সিগনেচারের পরে সেমিকোলন যোগ করার পরিবর্তে, আমরা কার্লি ব্র্যাকেট ব্যবহার করি এবং নির্দিষ্ট টাইপের জন্য trait-এর মেথডগুলোর যে নির্দিষ্ট আচরণ আমরা চাই তা দিয়ে মেথড বডি পূরণ করি।
এখন যেহেতু লাইব্রেরিটি NewsArticle
এবং SocialPost
-এর উপর Summary
trait ইমপ্লিমেন্ট করেছে, ক্রেটের ব্যবহারকারীরা NewsArticle
এবং SocialPost
-এর ইনস্ট্যান্সের উপর trait মেথডগুলো কল করতে পারবে ঠিক যেভাবে আমরা সাধারণ মেথড কল করি। একমাত্র পার্থক্য হলো ব্যবহারকারীকে অবশ্যই trait এবং টাইপ উভয়কেই স্কোপে আনতে হবে। এখানে একটি উদাহরণ দেওয়া হলো যে কীভাবে একটি বাইনারি ক্রেট আমাদের aggregator
লাইব্রেরি ক্রেট ব্যবহার করতে পারে:
use aggregator::{SocialPost, Summary};
fn main() {
let post = SocialPost {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
repost: false,
};
println!("1 new post: {}", post.summarize());
}
এই কোডটি প্রিন্ট করে 1 new post: horse_ebooks: of course, as you probably already know, people
।
aggregator
ক্রেটের উপর নির্ভরশীল অন্যান্য ক্রেটগুলোও তাদের নিজস্ব টাইপের উপর Summary
ইমপ্লিমেন্ট করার জন্য Summary
trait-টিকে স্কোপে আনতে পারে। একটি সীমাবদ্ধতা মনে রাখতে হবে যে আমরা একটি টাইপের উপর একটি trait শুধুমাত্র তখনই ইমপ্লিমেন্ট করতে পারি যদি trait অথবা টাইপ, বা উভয়ই, আমাদের ক্রেটের জন্য লোকাল (local) হয়। উদাহরণস্বরূপ, আমরা আমাদের aggregator
ক্রেটের কার্যকারিতার অংশ হিসেবে SocialPost
-এর মতো একটি কাস্টম টাইপের উপর Display
-এর মতো স্ট্যান্ডার্ড লাইব্রেরি trait ইমপ্লিমেন্ট করতে পারি কারণ SocialPost
টাইপটি আমাদের aggregator
ক্রেটের জন্য লোকাল। আমরা আমাদের aggregator
ক্রেটে Vec<T>
-এর উপর Summary
ইমপ্লিমেন্ট করতে পারি কারণ Summary
trait-টি আমাদের aggregator
ক্রেটের জন্য লোকাল।
কিন্তু আমরা এক্সটার্নাল টাইপের উপর এক্সটার্নাল trait ইমপ্লিমেন্ট করতে পারি না। উদাহরণস্বরূপ, আমরা আমাদের aggregator
ক্রেটের মধ্যে Vec<T>
-এর উপর Display
trait ইমপ্লিমেন্ট করতে পারি না কারণ Display
এবং Vec<T>
উভয়ই স্ট্যান্ডার্ড লাইব্রেরিতে ডিফাইন করা এবং আমাদের aggregator
ক্রেটের জন্য লোকাল নয়। এই সীমাবদ্ধতাটি coherence নামক একটি বৈশিষ্ট্যের অংশ, এবং আরও নির্দিষ্টভাবে orphan rule নামে পরিচিত, কারণ প্যারেন্ট টাইপ উপস্থিত নেই। এই নিয়মটি নিশ্চিত করে যে অন্য লোকের কোড আপনার কোড ভাঙতে পারবে না এবং বিপরীতক্রমেও। এই নিয়ম ছাড়া, দুটি ক্রেট একই টাইপের জন্য একই trait ইমপ্লিমেন্ট করতে পারত, এবং Rust জানত না কোন ইমপ্লিমেন্টেশনটি ব্যবহার করতে হবে।
ডিফল্ট ইমপ্লিমেন্টেশন (Default Implementations)
কখনও কখনও একটি trait-এর কিছু বা সমস্ত মেথডের জন্য ডিফল্ট আচরণ থাকা দরকারী, প্রতিটি টাইপের উপর সমস্ত মেথডের জন্য ইমপ্লিমেন্টেশন দাবি করার পরিবর্তে। তারপর, যখন আমরা কোনো নির্দিষ্ট টাইপের উপর trait-টি ইমপ্লিমেন্ট করি, তখন আমরা প্রতিটি মেথডের ডিফল্ট আচরণ রাখতে বা ওভাররাইড করতে পারি।
লিস্টিং ১০-১৪-এ, আমরা Summary
trait-এর summarize
মেথডের জন্য একটি ডিফল্ট স্ট্রিং নির্দিষ্ট করি, শুধুমাত্র মেথড সিগনেচার ডিফাইন করার পরিবর্তে, যেমনটি আমরা লিস্টিং ১০-১২-এ করেছিলাম।
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
NewsArticle
-এর ইনস্ট্যান্সগুলোর সারাংশ তৈরি করতে ডিফল্ট ইমপ্লিমেন্টেশন ব্যবহার করার জন্য, আমরা impl Summary for NewsArticle {}
দিয়ে একটি খালি impl
ব্লক নির্দিষ্ট করি।
যদিও আমরা আর সরাসরি NewsArticle
-এ summarize
মেথড ডিফাইন করছি না, আমরা একটি ডিফল্ট ইমপ্লিমেন্টেশন সরবরাহ করেছি এবং নির্দিষ্ট করেছি যে NewsArticle
Summary
trait ইমপ্লিমেন্ট করে। ফলস্বরূপ, আমরা এখনও একটি NewsArticle
-এর ইনস্ট্যান্সে summarize
মেথড কল করতে পারি, এভাবে:
use aggregator::{self, NewsArticle, Summary};
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
}
এই কোডটি New article available! (Read more...)
প্রিন্ট করে।
একটি ডিফল্ট ইমপ্লিমেন্টেশন তৈরি করার জন্য লিস্টিং ১০-১৩-এর SocialPost
-এ Summary
-এর ইমপ্লিমেন্টেশন সম্পর্কে আমাদের কিছু পরিবর্তন করতে হবে না। কারণটি হলো একটি ডিফল্ট ইমপ্লিমেন্টেশন ওভাররাইড করার সিনট্যাক্স এবং যে trait মেথডের ডিফল্ট ইমপ্লিমেন্টেশন নেই তা ইমপ্লিমেন্ট করার সিনট্যাক্স একই।
ডিফল্ট ইমপ্লিমেন্টেশনগুলো একই trait-এর অন্যান্য মেথড কল করতে পারে, এমনকি যদি সেই অন্যান্য মেথডগুলোর ডিফল্ট ইমপ্লিমেন্টেশন না থাকে। এইভাবে, একটি trait অনেক দরকারী কার্যকারিতা সরবরাহ করতে পারে এবং ইমপ্লিমেন্টরদের শুধুমাত্র একটি ছোট অংশ নির্দিষ্ট করতে হয়। উদাহরণস্বরূপ, আমরা Summary
trait-কে এমনভাবে ডিফাইন করতে পারি যাতে একটি summarize_author
মেথড থাকে যার ইমপ্লিমেন্টেশন আবশ্যক, এবং তারপর একটি summarize
মেথড ডিফাইন করতে পারি যার একটি ডিফল্ট ইমপ্লিমেন্টেশন থাকে যা summarize_author
মেথডকে কল করে:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
Summary
-এর এই সংস্করণটি ব্যবহার করতে, আমাদের কেবল একটি টাইপের উপর trait ইমপ্লিমেন্ট করার সময় summarize_author
ডিফাইন করতে হবে:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
আমরা summarize_author
ডিফাইন করার পরে, আমরা SocialPost
struct-এর ইনস্ট্যান্সগুলিতে summarize
কল করতে পারি, এবং summarize
-এর ডিফল্ট ইমপ্লিমেন্টেশনটি আমাদের সরবরাহ করা summarize_author
-এর ডেফিনিশনকে কল করবে। যেহেতু আমরা summarize_author
ইমপ্লিমেন্ট করেছি, Summary
trait আমাদের summarize
মেথডের আচরণ দিয়েছে কোনো অতিরিক্ত কোড লেখা ছাড়াই। এটি দেখতে এরকম:
use aggregator::{self, SocialPost, Summary};
fn main() {
let post = SocialPost {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
repost: false,
};
println!("1 new post: {}", post.summarize());
}
এই কোডটি প্রিন্ট করে 1 new post: (Read more from @horse_ebooks...)
।
মনে রাখবেন যে একই মেথডের একটি ওভাররাইডিং ইমপ্লিমেন্টেশন থেকে ডিফল্ট ইমপ্লিমেন্টেশন কল করা সম্ভব নয়।
প্যারামিটার হিসাবে ট্রেইট (Traits as Parameters)
এখন আপনি জানেন কিভাবে trait ডিফাইন এবং ইমপ্লিমেন্ট করতে হয়, আমরা এখন দেখব কিভাবে trait ব্যবহার করে এমন ফাংশন ডিফাইন করা যায় যা বিভিন্ন ধরনের টাইপ গ্রহণ করতে পারে। আমরা লিস্টিং ১০-১৩-এ NewsArticle
এবং SocialPost
টাইপের উপর ইমপ্লিমেন্ট করা Summary
trait ব্যবহার করে একটি notify
ফাংশন ডিফাইন করব যা তার item
প্যারামিটারে summarize
মেথড কল করে, যা এমন কোনো টাইপের যা Summary
trait ইমপ্লিমেন্ট করে। এটি করার জন্য, আমরা impl Trait
সিনট্যাক্স ব্যবহার করি, এভাবে:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
item
প্যারামিটারের জন্য একটি concrete টাইপের পরিবর্তে, আমরা impl
কিওয়ার্ড এবং trait-এর নাম নির্দিষ্ট করি। এই প্যারামিটারটি নির্দিষ্ট trait ইমপ্লিমেন্ট করে এমন যেকোনো টাইপ গ্রহণ করে। notify
-এর বডিতে, আমরা item
-এর উপর Summary
trait থেকে আসা যেকোনো মেথড কল করতে পারি, যেমন summarize
। আমরা notify
কল করতে পারি এবং যেকোনো NewsArticle
বা SocialPost
-এর ইনস্ট্যান্স পাস করতে পারি। যে কোড String
বা i32
-এর মতো অন্য কোনো টাইপ দিয়ে ফাংশনটি কল করবে, তা কম্পাইল হবে না কারণ সেই টাইপগুলো Summary
ইমপ্লিমেন্ট করে না।
ট্রেইট বাউন্ড সিনট্যাক্স (Trait Bound Syntax)
impl Trait
সিনট্যাক্স সহজ ক্ষেত্রে কাজ করে তবে এটি আসলে একটি দীর্ঘ ফর্মের জন্য সিনট্যাক্স সুগার যা trait bound নামে পরিচিত; এটি দেখতে এইরকম:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
এই দীর্ঘ ফর্মটি পূর্ববর্তী বিভাগের উদাহরণের সমতুল্য তবে আরও ভার্বোস। আমরা জেনেরিক টাইপ প্যারামিটারের ডিক্লেয়ারেশনের সাথে একটি কোলনের পরে এবং অ্যাঙ্গেল ব্র্যাকেটের ভিতরে trait bounds রাখি।
impl Trait
সিনট্যাক্স সুবিধাজনক এবং সহজ ক্ষেত্রে কোডকে আরও সংক্ষিপ্ত করে তোলে, যেখানে সম্পূর্ণ trait bound সিনট্যাক্স অন্যান্য ক্ষেত্রে আরও জটিলতা প্রকাশ করতে পারে। উদাহরণস্বরূপ, আমাদের দুটি প্যারামিটার থাকতে পারে যা Summary
ইমপ্লিমেন্ট করে। impl Trait
সিনট্যাক্স দিয়ে এটি করতে হলে দেখতে এমন হবে:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
impl Trait
ব্যবহার করা উপযুক্ত যদি আমরা এই ফাংশনটিকে item1
এবং item2
-কে ভিন্ন টাইপের হতে দিতে চাই (যতক্ষণ উভয় টাইপ Summary
ইমপ্লিমেন্ট করে)। যদি আমরা উভয় প্যারামিটারকে একই টাইপের হতে বাধ্য করতে চাই, তবে আমাদের অবশ্যই একটি trait bound ব্যবহার করতে হবে, যেমন:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
item1
এবং item2
প্যারামিটারের টাইপ হিসাবে নির্দিষ্ট করা জেনেরিক টাইপ T
ফাংশনটিকে এমনভাবে সীমাবদ্ধ করে যে item1
এবং item2
-এর জন্য আর্গুমেন্ট হিসাবে পাস করা মানের concrete টাইপ অবশ্যই একই হতে হবে।
+
সিনট্যাক্স দিয়ে একাধিক ট্রেইট বাউন্ড নির্দিষ্ট করা
আমরা একাধিক trait bound-ও নির্দিষ্ট করতে পারি। ধরা যাক আমরা চাই notify
item
-এ summarize
-এর পাশাপাশি ডিসপ্লে ফরম্যাটিংও ব্যবহার করুক: আমরা notify
ডেফিনিশনে নির্দিষ্ট করি যে item
অবশ্যই Display
এবং Summary
উভয়ই ইমপ্লিমেন্ট করবে। আমরা +
সিনট্যাক্স ব্যবহার করে এটি করতে পারি:
pub fn notify(item: &(impl Summary + Display)) {
+
সিনট্যাক্স জেনেরিক টাইপের উপর trait bounds-এর সাথেও বৈধ:
pub fn notify<T: Summary + Display>(item: &T) {
দুটি trait bounds নির্দিষ্ট করার সাথে, notify
-এর বডি summarize
কল করতে পারে এবং item
ফরম্যাট করতে {}
ব্যবহার করতে পারে।
where
ক্লজ দিয়ে পরিষ্কার ট্রেইট বাউন্ড
অতিরিক্ত trait bounds ব্যবহার করার কিছু অসুবিধা আছে। প্রতিটি জেনেরিকের নিজস্ব trait bounds থাকে, তাই একাধিক জেনেরিক টাইপ প্যারামিটারযুক্ত ফাংশনগুলোতে ফাংশনের নাম এবং তার প্যারামিটার তালিকার মধ্যে প্রচুর trait bound তথ্য থাকতে পারে, যা ফাংশন সিগনেচার পড়া কঠিন করে তোলে। এই কারণে, Rust-এর ফাংশন সিগনেচারের পরে একটি where
ক্লজের ভিতরে trait bounds নির্দিষ্ট করার জন্য বিকল্প সিনট্যাক্স রয়েছে। সুতরাং, এটি লেখার পরিবর্তে:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
আমরা একটি where
ক্লজ ব্যবহার করতে পারি, এভাবে:
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
unimplemented!()
}
এই ফাংশনের সিগনেচারটি কম জটীল: ফাংশনের নাম, প্যারামিটার তালিকা এবং রিটার্ন টাইপ কাছাকাছি রয়েছে, অনেকটা trait bounds ছাড়া একটি ফাংশনের মতো।
ট্রেইট ইমপ্লিমেন্ট করে এমন টাইপ রিটার্ন করা
আমরা রিটার্ন পজিশনে impl Trait
সিনট্যাক্স ব্যবহার করে এমন একটি টাইপের মান রিটার্ন করতে পারি যা একটি trait ইমপ্লিমেন্ট করে, যেমনটি এখানে দেখানো হয়েছে:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable() -> impl Summary {
SocialPost {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
repost: false,
}
}
রিটার্ন টাইপের জন্য impl Summary
ব্যবহার করে, আমরা নির্দিষ্ট করি যে returns_summarizable
ফাংশনটি Summary
trait ইমপ্লিমেন্ট করে এমন কোনো টাইপ রিটার্ন করে, concrete টাইপের নাম উল্লেখ না করেই। এই ক্ষেত্রে, returns_summarizable
একটি SocialPost
রিটার্ন করে, কিন্তু এই ফাংশন কল করা কোডকে এটি জানার প্রয়োজন নেই।
শুধুমাত্র যে trait ইমপ্লিমেন্ট করে তা দ্বারা একটি রিটার্ন টাইপ নির্দিষ্ট করার ক্ষমতাটি ক্লোজার এবং ইটারেটরের প্রসঙ্গে বিশেষভাবে কার্যকর, যা আমরা চ্যাপ্টার ১৩-এ আলোচনা করব। ক্লোজার এবং ইটারেটর এমন টাইপ তৈরি করে যা কেবল কম্পাইলার জানে বা যে টাইপগুলো নির্দিষ্ট করা খুব দীর্ঘ। impl Trait
সিনট্যাক্স আপনাকে সংক্ষিপ্তভাবে নির্দিষ্ট করতে দেয় যে একটি ফাংশন Iterator
trait ইমপ্লিমেন্ট করে এমন কোনো টাইপ রিটার্ন করে, একটি খুব দীর্ঘ টাইপ লেখার প্রয়োজন ছাড়াই।
তবে, আপনি কেবল impl Trait
ব্যবহার করতে পারেন যদি আপনি একটি একক টাইপ রিটার্ন করেন। উদাহরণস্বরূপ, এই কোডটি যা impl Summary
হিসাবে রিটার্ন টাইপ নির্দিষ্ট করে একটি NewsArticle
বা একটি SocialPost
রিটার্ন করে, কাজ করবে না:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}
impl Summary for SocialPost {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
SocialPost {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
repost: false,
}
}
}
কম্পাইলারে impl Trait
সিনট্যাক্স কীভাবে ইমপ্লিমেন্ট করা হয়েছে তার সীমাবদ্ধতার কারণে একটি NewsArticle
বা একটি SocialPost
রিটার্ন করা অনুমোদিত নয়। আমরা চ্যাপ্টার ১৮-এর “ভিন্ন ধরনের মানের জন্য ট্রেইট অবজেক্ট ব্যবহার করা” বিভাগে এই আচরণ সহ একটি ফাংশন কীভাবে লিখতে হয় তা আলোচনা করব।
ট্রেইট বাউন্ড ব্যবহার করে শর্তসাপেক্ষে মেথড ইমপ্লিমেন্ট করা
জেনেরিক টাইপ প্যারামিটার ব্যবহার করে একটি impl
ব্লকের সাথে একটি trait bound ব্যবহার করে, আমরা নির্দিষ্ট trait ইমপ্লিমেন্ট করে এমন টাইপগুলোর জন্য শর্তসাপেক্ষে মেথড ইমপ্লিমেন্ট করতে পারি। উদাহরণস্বরূপ, লিস্টিং ১০-১৫-এর Pair<T>
টাইপটি সর্বদা Pair<T>
-এর একটি নতুন ইনস্ট্যান্স রিটার্ন করার জন্য new
ফাংশন ইমপ্লিমেন্ট করে (চ্যাপ্টার ৫-এর “মেথড ডিফাইন করা” বিভাগ থেকে মনে করুন যে Self
হলো impl
ব্লকের টাইপের একটি টাইপ অ্যালিয়াস, যা এই ক্ষেত্রে Pair<T>
)। কিন্তু পরবর্তী impl
ব্লকে, Pair<T>
কেবল তখনই cmp_display
মেথডটি ইমপ্লিমেন্ট করে যদি তার অভ্যন্তরীণ টাইপ T
PartialOrd
trait ইমপ্লিমেন্ট করে যা তুলনা সক্ষম করে এবং Display
trait ইমপ্লিমেন্ট করে যা প্রিন্টিং সক্ষম করে।
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
আমরা trait bounds সন্তুষ্ট করে এমন যেকোনো টাইপের জন্য একটি trait শর্তসাপেক্ষে ইমপ্লিমেন্ট করতে পারি। এই ধরনের ইমপ্লিমেন্টেশনকে blanket implementations বলা হয় এবং এটি Rust স্ট্যান্ডার্ড লাইব্রেরিতে ব্যাপকভাবে ব্যবহৃত হয়। উদাহরণস্বরূপ, স্ট্যান্ডার্ড লাইব্রেরি Display
trait ইমপ্লিমেন্ট করে এমন যেকোনো টাইপের উপর ToString
trait ইমপ্লিমেন্ট করে। স্ট্যান্ডার্ড লাইব্রেরিতে impl
ব্লকটি এই কোডের মতো দেখতে:
impl<T: Display> ToString for T {
// --snip--
}
যেহেতু স্ট্যান্ডার্ড লাইব্রেরিতে এই blanket implementation রয়েছে, আমরা Display
trait ইমপ্লিমেন্ট করে এমন যেকোনো টাইপের উপর ToString
trait দ্বারা সংজ্ঞায়িত to_string
মেথড কল করতে পারি। উদাহরণস্বরূপ, আমরা পূর্ণসংখ্যাকে তাদের সংশ্লিষ্ট String
মানে পরিণত করতে পারি কারণ পূর্ণসংখ্যা Display
ইমপ্লিমেন্ট করে:
#![allow(unused)] fn main() { let s = 3.to_string(); }
Blanket implementation গুলি trait-এর ডকুমেন্টেশনে “Implementors” বিভাগে উপস্থিত থাকে।
Traits এবং trait bounds আমাদের জেনেরিক টাইপ প্যারামিটার ব্যবহার করে কোড লিখতে দেয় যা পুনরাবৃত্তি কমায় কিন্তু কম্পাইলারকে নির্দিষ্ট করে দেয় যে আমরা জেনেরিক টাইপের নির্দিষ্ট আচরণ চাই। কম্পাইলার তখন trait bound তথ্য ব্যবহার করে পরীক্ষা করতে পারে যে আমাদের কোডের সাথে ব্যবহৃত সমস্ত concrete টাইপ সঠিক আচরণ প্রদান করে। ডাইনামিক্যালি টাইপড ল্যাঙ্গুয়েজে, আমরা যদি এমন একটি টাইপের উপর একটি মেথড কল করি যা মেথডটি ডিফাইন করে না, তবে আমরা রানটাইমে একটি এরর পেতাম। কিন্তু Rust এই এররগুলিকে কম্পাইল টাইমে নিয়ে আসে যাতে আমরা আমাদের কোড রান করতে সক্ষম হওয়ার আগেই সমস্যাগুলি সমাধান করতে বাধ্য হই। উপরন্তু, আমাদের রানটাইমে আচরণের জন্য পরীক্ষা করার কোড লিখতে হবে না কারণ আমরা ইতিমধ্যে কম্পাইল টাইমে পরীক্ষা করে ফেলেছি। এটি জেনেরিক্সের নমনীয়তা ত্যাগ না করেই পারফরম্যান্স উন্নত করে।