ট্রেইট: সাধারণ আচরণ সংজ্ঞায়িত করা (Traits: Defining Shared Behavior)

একটি ট্রেইট (trait) একটি নির্দিষ্ট টাইপের কার্যকারিতা সংজ্ঞায়িত করে এবং অন্যান্য টাইপের সাথে শেয়ার করতে পারে। আমরা অ্যাবস্ট্রাক্ট উপায়ে সাধারণ আচরণ সংজ্ঞায়িত করতে ট্রেইট ব্যবহার করতে পারি। আমরা ট্রেইট বাউন্ড (trait bounds) ব্যবহার করে নির্দিষ্ট করতে পারি যে একটি জেনেরিক টাইপ যেকোনো টাইপ হতে পারে যার নির্দিষ্ট আচরণ রয়েছে।

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

একটি ট্রেইট সংজ্ঞায়িত করা (Defining a Trait)

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

উদাহরণস্বরূপ, ধরা যাক আমাদের কাছে একাধিক স্ট্রাকট রয়েছে যা বিভিন্ন ধরণের এবং পরিমাণের টেক্সট ধারণ করে: একটি NewsArticle স্ট্রাকট যা একটি নির্দিষ্ট স্থানে ফাইল করা একটি সংবাদ ধারণ করে এবং একটি SocialPost যাতে সর্বাধিক 280টি অক্ষর থাকতে পারে, সাথে মেটাডেটা যা নির্দেশ করে যে এটি একটি নতুন পোস্ট, একটি রিপোস্ট, নাকি অন্য কোনো পোস্টের উত্তর।

আমরা aggregator নামে একটি মিডিয়া অ্যাগ্রিগেটর লাইব্রেরি ক্রেট তৈরি করতে চাই যা NewsArticle বা SocialPost ইন্সট্যান্সে সংরক্ষিত ডেটার সারাংশ প্রদর্শন করতে পারে। এটি করার জন্য, আমাদের প্রতিটি টাইপ থেকে একটি সারাংশ প্রয়োজন, এবং আমরা একটি ইন্সট্যান্সে একটি summarize মেথড কল করে সেই সারাংশের অনুরোধ করব। Listing 10-12 একটি পাবলিক Summary ট্রেইটের সংজ্ঞা দেখায় যা এই আচরণটিকে প্রকাশ করে।

pub trait Summary {
    fn summarize(&self) -> String;
}

এখানে, আমরা trait কীওয়ার্ড এবং তারপর ট্রেইটের নাম ব্যবহার করে একটি ট্রেইট ঘোষণা করি, যা এই ক্ষেত্রে Summary। আমরা ট্রেইটটিকে pub হিসাবেও ঘোষণা করি যাতে এই ক্রেটের উপর নির্ভরশীল ক্রেটগুলোও এই ট্রেইটটি ব্যবহার করতে পারে, যেমনটি আমরা কয়েকটি উদাহরণে দেখব। কোঁকড়া ধনুর্বন্ধনীর ভিতরে, আমরা মেথড সিগনেচারগুলো ঘোষণা করি যা এই ট্রেইটটি ইমপ্লিমেন্ট করে এমন টাইপগুলোর আচরণ বর্ণনা করে, যা এই ক্ষেত্রে fn summarize(&self) -> String

মেথড সিগনেচারের পরে, কোঁকড়া ধনুর্বন্ধনীর মধ্যে একটি ইমপ্লিমেন্টেশন দেওয়ার পরিবর্তে, আমরা একটি সেমিকোলন ব্যবহার করি। এই ট্রেইটটি ইমপ্লিমেন্ট করা প্রতিটি টাইপকে অবশ্যই মেথডের বডির জন্য নিজস্ব কাস্টম আচরণ প্রদান করতে হবে। কম্পাইলার এনফোর্স (enforce) করবে যে Summary ট্রেইট আছে এমন যেকোনো টাইপের অবশ্যই summarize মেথড সংজ্ঞায়িত থাকবে এবং সিগনেচার হুবহু একই হবে।

একটি ট্রেইটের বডিতে একাধিক মেথড থাকতে পারে: মেথড সিগনেচারগুলো প্রতি লাইনে তালিকাভুক্ত করা হয় এবং প্রতিটি লাইন একটি সেমিকোলন দিয়ে শেষ হয়।

একটি টাইপে একটি ট্রেইট ইমপ্লিমেন্ট করা (Implementing a Trait on a Type)

এখন আমরা Summary ট্রেইটের মেথডগুলোর কাঙ্ক্ষিত সিগনেচার সংজ্ঞায়িত করেছি, আমরা এটিকে আমাদের মিডিয়া অ্যাগ্রিগেটরের টাইপগুলোতে ইমপ্লিমেন্ট করতে পারি। Listing 10-13 NewsArticle স্ট্রাকটে Summary ট্রেইটের একটি ইমপ্লিমেন্টেশন দেখায় যা হেডলাইন, লেখক এবং অবস্থান ব্যবহার করে summarize-এর রিটার্ন ভ্যালু তৈরি করে। SocialPost স্ট্রাকটের জন্য, আমরা summarize-কে ব্যবহারকারীর নাম এবং তারপর পোস্টের সম্পূর্ণ টেক্সট হিসাবে সংজ্ঞায়িত করি, ধরে নিই যে পোস্টের কনটেন্ট ইতিমধ্যেই 280 অক্ষরে সীমাবদ্ধ।

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

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

এখন লাইব্রেরিটি NewsArticle এবং SocialPost-এ Summary ট্রেইট ইমপ্লিমেন্ট করেছে, ক্রেটের ব্যবহারকারীরা NewsArticle এবং SocialPost-এর ইন্সট্যান্সগুলোতে ট্রেইট মেথডগুলোকে একইভাবে কল করতে পারে যেভাবে আমরা নিয়মিত মেথড কল করি। একমাত্র পার্থক্য হল ব্যবহারকারীকে অবশ্যই ট্রেইটটিকে টাইপের মতোই স্কোপে আনতে হবে। এখানে একটি বাইনারি ক্রেট কীভাবে আমাদের 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 social post: {}", post.summarize());
}

এই কোডটি 1 new post: horse_ebooks: of course, as you probably already know, people প্রিন্ট করে।

অন্যান্য ক্রেট যেগুলো aggregator ক্রেটের উপর নির্ভর করে তারাও Summary ট্রেইটটিকে স্কোপে এনে তাদের নিজস্ব টাইপগুলোতে Summary ইমপ্লিমেন্ট করতে পারে। একটি সীমাবদ্ধতা লক্ষ্য করার মতো যে আমরা শুধুমাত্র তখনই একটি টাইপের উপর একটি ট্রেইট ইমপ্লিমেন্ট করতে পারি যদি ট্রেইট বা টাইপ, অথবা উভয়ই, আমাদের ক্রেটের লোকাল হয়। উদাহরণস্বরূপ, আমরা স্ট্যান্ডার্ড লাইব্রেরি ট্রেইট যেমন Display-কে আমাদের aggregator ক্রেট কার্যকারিতার অংশ হিসাবে SocialPost-এর মতো কাস্টম টাইপে ইমপ্লিমেন্ট করতে পারি কারণ SocialPost টাইপটি আমাদের aggregator ক্রেটের লোকাল। আমরা আমাদের aggregator ক্রেটে Vec<T>-তে Summary ইমপ্লিমেন্ট করতে পারি কারণ Summary ট্রেইটটি আমাদের aggregator ক্রেটের লোকাল।

কিন্তু আমরা এক্সটার্নাল টাইপগুলোতে এক্সটার্নাল ট্রেইট ইমপ্লিমেন্ট করতে পারি না। উদাহরণস্বরূপ, আমরা আমাদের aggregator ক্রেটের মধ্যে Vec<T>-তে Display ট্রেইট ইমপ্লিমেন্ট করতে পারি না কারণ Display এবং Vec<T> উভয়ই স্ট্যান্ডার্ড লাইব্রেরিতে সংজ্ঞায়িত করা হয়েছে এবং আমাদের aggregator ক্রেটের লোকাল নয়। এই সীমাবদ্ধতাটি কোহেরেন্স (coherence) নামক একটি বৈশিষ্ট্যের অংশ, এবং আরও নির্দিষ্টভাবে অর্ফান রুল (orphan rule), এটির নামকরণ করা হয়েছে কারণ প্যারেন্ট টাইপ উপস্থিত নেই। এই নিয়মটি নিশ্চিত করে যে অন্য লোকেদের কোড আপনার কোড ভাঙতে পারবে না এবং এর বিপরীতটিও সত্য। নিয়মটি ছাড়া, দুটি ক্রেট একই টাইপের জন্য একই ট্রেইট ইমপ্লিমেন্ট করতে পারত এবং Rust জানত না কোন ইমপ্লিমেন্টেশন ব্যবহার করতে হবে।

ডিফল্ট ইমপ্লিমেন্টেশন (Default Implementations)

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

Listing 10-14-এ, আমরা Summary ট্রেইটের summarize মেথডের জন্য শুধুমাত্র মেথড সিগনেচার সংজ্ঞায়িত করার পরিবর্তে একটি ডিফল্ট স্ট্রিং নির্দিষ্ট করি, যেমনটি আমরা Listing 10-12-তে করেছি।

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 ট্রেইট ইমপ্লিমেন্ট করে। ফলস্বরূপ, আমরা এখনও 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...) প্রিন্ট করে।

একটি ডিফল্ট ইমপ্লিমেন্টেশন তৈরি করা Listing 10-13-এ SocialPost-এ Summary-এর ইমপ্লিমেন্টেশন সম্পর্কে আমাদের কোনো কিছু পরিবর্তন করার প্রয়োজন নেই। কারণ হল একটি ডিফল্ট ইমপ্লিমেন্টেশন ওভাররাইড করার সিনট্যাক্স একটি ট্রেইট মেথড ইমপ্লিমেন্ট করার সিনট্যাক্সের মতোই যা একটি ডিফল্ট ইমপ্লিমেন্টেশন নেই।

ডিফল্ট ইমপ্লিমেন্টেশনগুলো একই ট্রেইটের অন্যান্য মেথডগুলোকে কল করতে পারে, এমনকী যদি সেই অন্য মেথডগুলোর ডিফল্ট ইমপ্লিমেন্টেশন না থাকে। এইভাবে, একটি ট্রেইট অনেক দরকারী কার্যকারিতা সরবরাহ করতে পারে এবং ইমপ্লিমেন্টরদের কেবল এটির একটি ছোট অংশ নির্দিষ্ট করতে হয়। উদাহরণস্বরূপ, আমরা Summary ট্রেইটটিকে একটি 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-এর এই ভার্সনটি ব্যবহার করার জন্য, আমাদের শুধুমাত্র একটি টাইপের উপর ট্রেইট ইমপ্লিমেন্ট করার সময় 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 স্ট্রাকটের ইন্সট্যান্সগুলোতে summarize কল করতে পারি এবং summarize-এর ডিফল্ট ইমপ্লিমেন্টেশনটি আমাদের দেওয়া summarize_author-এর সংজ্ঞাকে কল করবে। যেহেতু আমরা summarize_author ইমপ্লিমেন্ট করেছি, তাই Summary ট্রেইট আমাদের আরও কোনো কোড লেখার প্রয়োজন ছাড়াই 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 social post: {}", post.summarize());
}

এই কোডটি 1 new post: (Read more from @horse_ebooks...) প্রিন্ট করে।

মনে রাখবেন যে একই মেথডের ওভাররাইডিং ইমপ্লিমেন্টেশন থেকে ডিফল্ট ইমপ্লিমেন্টেশনকে কল করা সম্ভব নয়।

প্যারামিটার হিসাবে ট্রেইট (Traits as Parameters)

এখন আপনি জানেন কিভাবে ট্রেইট সংজ্ঞায়িত এবং ইমপ্লিমেন্ট করতে হয়, আমরা এমন ফাংশন সংজ্ঞায়িত করতে ট্রেইটগুলো কীভাবে ব্যবহার করতে হয় তা অন্বেষণ করতে পারি যা বিভিন্ন টাইপ গ্রহণ করে। আমরা Listing 10-13-এ NewsArticle এবং SocialPost টাইপগুলোতে যে Summary ট্রেইট ইমপ্লিমেন্ট করেছি তা ব্যবহার করে একটি notify ফাংশন সংজ্ঞায়িত করব যা তার item প্যারামিটারে summarize মেথড কল করে, যেটি কিছু টাইপের যা Summary ট্রেইট ইমপ্লিমেন্ট করে। এটি করার জন্য, আমরা 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 প্যারামিটারের জন্য একটি কংক্রিট টাইপের পরিবর্তে, আমরা impl কীওয়ার্ড এবং ট্রেইটের নাম নির্দিষ্ট করি। এই প্যারামিটারটি নির্দিষ্ট ট্রেইট ইমপ্লিমেন্ট করে এমন যেকোনো টাইপ গ্রহণ করে। notify-এর বডিতে, আমরা item-এ Summary ট্রেইট থেকে আসা যেকোনো মেথড কল করতে পারি, যেমন 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());
}

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

impl Trait সিনট্যাক্সটি সুবিধাজনক এবং সহজ ক্ষেত্রে আরও সংক্ষিপ্ত কোড তৈরি করে, যেখানে সম্পূর্ণ ট্রেইট বাউন্ড সিনট্যাক্স অন্যান্য ক্ষেত্রে আরও জটিলতা প্রকাশ করতে পারে। উদাহরণস্বরূপ, আমাদের দুটি প্যারামিটার থাকতে পারে যা Summary ইমপ্লিমেন্ট করে। impl Trait সিনট্যাক্স দিয়ে এটি করা এইরকম দেখায়:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

যদি আমরা চাই যে এই ফাংশনটি item1 এবং item2-কে বিভিন্ন টাইপের অনুমতি দিক (যতক্ষণ উভয় টাইপ Summary ইমপ্লিমেন্ট করে) ততক্ষণ impl Trait ব্যবহার করা উপযুক্ত। যাইহোক, যদি আমরা উভয় প্যারামিটারকে একই টাইপ হতে বাধ্য করতে চাই, তাহলে আমাদের অবশ্যই একটি ট্রেইট বাউন্ড ব্যবহার করতে হবে, এইভাবে:

pub fn notify<T: Summary>(item1: &T, item2: &T) {

item1 এবং item2 প্যারামিটারের টাইপ হিসাবে নির্দিষ্ট করা জেনেরিক টাইপ T ফাংশনটিকে সীমাবদ্ধ করে যাতে item1 এবং item2-এর জন্য আর্গুমেন্ট হিসাবে পাস করা মানের কংক্রিট টাইপ একই হতে হবে।

+ সিনট্যাক্স দিয়ে একাধিক ট্রেইট বাউন্ড নির্দিষ্ট করা (Specifying Multiple Trait Bounds with the + Syntax)

আমরা একাধিক ট্রেইট বাউন্ডও নির্দিষ্ট করতে পারি। ধরা যাক, আমরা চেয়েছিলাম notify item-এ ডিসপ্লে ফরম্যাটিং এবং summarize ব্যবহার করুক: আমরা notify সংজ্ঞায় নির্দিষ্ট করি যে item-কে অবশ্যই Display এবং Summary উভয়ই ইমপ্লিমেন্ট করতে হবে। আমরা + সিনট্যাক্স ব্যবহার করে এটি করতে পারি:

pub fn notify(item: &(impl Summary + Display)) {

+ সিনট্যাক্স জেনেরিক টাইপের উপর ট্রেইট বাউন্ডের সাথেও বৈধ:

pub fn notify<T: Summary + Display>(item: &T) {

দুটি ট্রেইট বাউন্ড নির্দিষ্ট করার সাথে, notify-এর বডি summarize কল করতে পারে এবং item ফর্ম্যাট করতে {} ব্যবহার করতে পারে।

where ক্লজ সহ ক্লিনার ট্রেইট বাউন্ড (Clearer Trait Bounds with where Clauses)

অত্যধিক ট্রেইট বাউন্ড ব্যবহার করার অসুবিধা রয়েছে। প্রতিটি জেনেরিকের নিজস্ব ট্রেইট বাউন্ড রয়েছে, তাই একাধিক জেনেরিক টাইপ প্যারামিটারযুক্ত ফাংশনগুলোতে ফাংশনের নাম এবং এর প্যারামিটার তালিকার মধ্যে প্রচুর ট্রেইট বাউন্ডের তথ্য থাকতে পারে, যা ফাংশন সিগনেচারকে পড়া কঠিন করে তোলে। এই কারণে, Rust-এর ফাংশন সিগনেচারের পরে একটি where ক্লজের মধ্যে ট্রেইট বাউন্ডগুলো নির্দিষ্ট করার জন্য বিকল্প সিনট্যাক্স রয়েছে। সুতরাং, এটি লেখার পরিবর্তে:

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!()
}

এই ফাংশনের সিগনেচারটি কম বিশৃঙ্খল: ফাংশনের নাম, প্যারামিটার তালিকা এবং রিটার্ন টাইপ কাছাকাছি, প্রচুর ট্রেইট বাউন্ড ছাড়া একটি ফাংশনের মতো।

ট্রেইট ইমপ্লিমেন্ট করে এমন টাইপ রিটার্ন করা (Returning Types That Implement Traits)

আমরা রিটার্ন পজিশনে 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)
    }
}

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 ট্রেইট ইমপ্লিমেন্ট করে এমন কিছু টাইপ রিটার্ন করে। এই ক্ষেত্রে, returns_summarizable একটি SocialPost রিটার্ন করে, কিন্তু এই ফাংশনটিকে কল করা কোডটি সেটি জানার প্রয়োজন নেই।

শুধুমাত্র যে ট্রেইটটি ইমপ্লিমেন্ট করে তার দ্বারা একটি রিটার্ন টাইপ নির্দিষ্ট করার ক্ষমতা ক্লোজার (closures) এবং ইটারেটরগুলোর (iterators) প্রসঙ্গে বিশেষভাবে কার্যকর, যা আমরা চ্যাপ্টার 13-এ কভার করব। ক্লোজার এবং ইটারেটরগুলো এমন টাইপ তৈরি করে যা শুধুমাত্র কম্পাইলার জানে বা টাইপগুলো উল্লেখ করার জন্য খুব দীর্ঘ। impl Trait সিনট্যাক্স আপনাকে সংক্ষিপ্তভাবে নির্দিষ্ট করতে দেয় যে একটি ফাংশন Iterator ট্রেইট ইমপ্লিমেন্ট করে এমন কিছু টাইপ রিটার্ন করে, খুব দীর্ঘ টাইপ লেখার প্রয়োজন ছাড়াই।

যাইহোক, আপনি শুধুমাত্র তখনই impl Trait ব্যবহার করতে পারেন যদি আপনি একটি একক টাইপ রিটার্ন করেন। উদাহরণস্বরূপ, এই কোডটি যা একটি NewsArticle বা একটি SocialPost রিটার্ন করে এবং রিটার্ন টাইপটিকে impl Summary হিসাবে নির্দিষ্ট করা হয়েছে তা কাজ করবে না:

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,
        }
    }
}

একটি NewsArticle বা একটি SocialPost রিটার্ন করার অনুমতি নেই কারণ কম্পাইলারে impl Trait সিনট্যাক্স কীভাবে ইমপ্লিমেন্ট করা হয় তার চারপাশে সীমাবদ্ধতা রয়েছে। এই আচরণ সহ একটি ফাংশন কীভাবে লিখতে হয় তা আমরা চ্যাপ্টার 18-এর “ভিন্ন টাইপের মানের জন্য অনুমতি দেয় এমন ট্রেইট অবজেক্ট ব্যবহার করা” বিভাগে কভার করব।

ট্রেইট বাউন্ড ব্যবহার করে শর্তসাপেক্ষে মেথড ইমপ্লিমেন্ট করা (Using Trait Bounds to Conditionally Implement Methods)

জেনেরিক টাইপ প্যারামিটার ব্যবহার করে এমন একটি impl ব্লকের সাথে একটি ট্রেইট বাউন্ড ব্যবহার করে, আমরা নির্দিষ্ট ট্রেইটগুলো ইমপ্লিমেন্ট করে এমন টাইপগুলোর জন্য শর্তসাপেক্ষে মেথড ইমপ্লিমেন্ট করতে পারি। উদাহরণস্বরূপ, Listing 10-15-এর Pair<T> টাইপটি সর্বদাই new ফাংশন ইমপ্লিমেন্ট করে Pair<T>-এর একটি নতুন ইন্সট্যান্স রিটার্ন করার জন্য (চ্যাপ্টার ৫-এর “মেথড সংজ্ঞায়িত করা” বিভাগ থেকে স্মরণ করুন যে Self হল impl ব্লকের টাইপের জন্য একটি টাইপ অ্যালিয়াস, যা এই ক্ষেত্রে Pair<T>)। কিন্তু পরবর্তী impl ব্লকে, Pair<T> শুধুমাত্র তখনই cmp_display মেথড ইমপ্লিমেন্ট করে যদি এর ভেতরের টাইপ T PartialOrd ট্রেইট ইমপ্লিমেন্ট করে যা তুলনা সক্ষম করে এবং Display ট্রেইট যা প্রিন্টিং সক্ষম করে।

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

আমরা যেকোনো টাইপের জন্য শর্তসাপেক্ষে একটি ট্রেইট ইমপ্লিমেন্ট করতে পারি যা অন্য একটি ট্রেইট ইমপ্লিমেন্ট করে। ট্রেইট বাউন্ড সন্তুষ্ট করে এমন যেকোনো টাইপের উপর একটি ট্রেইটের ইমপ্লিমেন্টেশনগুলোকে ব্ল্যাঙ্কেট ইমপ্লিমেন্টেশন (blanket implementations) বলা হয় এবং Rust স্ট্যান্ডার্ড লাইব্রেরিতে ব্যাপকভাবে ব্যবহৃত হয়। উদাহরণস্বরূপ, স্ট্যান্ডার্ড লাইব্রেরি যেকোনো টাইপের উপর ToString ট্রেইট ইমপ্লিমেন্ট করে যা Display ট্রেইট ইমপ্লিমেন্ট করে। স্ট্যান্ডার্ড লাইব্রেরির impl ব্লকটি এই কোডের মতো দেখায়:

impl<T: Display> ToString for T {
    // --snip--
}

যেহেতু স্ট্যান্ডার্ড লাইব্রেরিতে এই ব্ল্যাঙ্কেট ইমপ্লিমেন্টেশন রয়েছে, তাই আমরা ToString ট্রেইট দ্বারা সংজ্ঞায়িত to_string মেথডটিকে যেকোনো টাইপে কল করতে পারি যা Display ট্রেইট ইমপ্লিমেন্ট করে। উদাহরণস্বরূপ, আমরা ইন্টিজারগুলোকে তাদের সংশ্লিষ্ট String মানগুলোতে পরিণত করতে পারি কারণ ইন্টিজারগুলো Display ইমপ্লিমেন্ট করে:

#![allow(unused)]
fn main() {
let s = 3.to_string();
}

ব্ল্যাঙ্কেট ইমপ্লিমেন্টেশনগুলো ট্রেইটের জন্য ডকুমেন্টেশনের “Implementors” বিভাগে প্রদর্শিত হয়।

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