পাঠ ১০.২

Trait দিয়ে shared behavior define করা

Defining Shared Behavior with Traits

Trait বলে দেয় — কোনো type-এর কী method থাকা চাই। একাধিক type একই behavior provide করলে তাদের generic code-এ একসাথে handle করা যায়। অন্য language-এ এটাকে interface বলে — কিন্তু Rust-এ কিছু পার্থক্য আছে।

Trait define করা

ধরো — একটা media aggregator library; NewsArticleSocialPost দু'টা struct, দু'টোতেই একটা summary দরকার। Trait দিয়ে এই common behavior:

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

trait keyword + নাম + body-তে method signature। শেষে semicolon — body নেই, prototype-only। প্রতিটা implementing type নিজের body দেবে।

Trait implement করা

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

Syntax — impl TraitName for TypeName

Use:

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

Output: 1 new post: horse_ebooks: of course...

Orphan rule

Trait implement করতে — trait টা বা type টা অন্তত একটা তোমার crate-এর local হতে হবে।

  • ✅ Standard library-র Display trait তোমার SocialPost-এ implement।
  • ✅ তোমার Summary trait Vec<T>-এ implement।
  • Display trait Vec<T>-এ implement (দু'টোই external)।

এই rule-কে বলে coherence — অন্যের code তোমার break করতে পারবে না।

Default implementation

Trait-এ default body দেওয়া যায়:

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

Type override না করে default-ই use করতে পারে — empty impl block:

impl Summary for NewsArticle {}

এখন article.summarize() কল করলে "(Read more...)"

Default — অন্য method call করতে পারে

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

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

summarize_author required (no default); summarize default-এ সেটাই call করে। Implementing type শুধু summarize_author provide করলেই হবে।

Note: override-এ default implementation call করা যায় না — override মানে পুরোপুরি replace।

Trait parameter — impl Trait

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

&impl Summary = "যেকোনো type যা Summary implement করে"। Function-এর body-তে শুধু Summary-র method call করা যাবে।

এটা আসলে syntax sugar — পুরো form trait bound:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

একই কাজ। কখন কোনটা?

  • Simple, single param — impl Trait concise।
  • Complex bound, multiple param — trait bound স্পষ্ট।

একাধিক parameter-এ পার্থক্য:

// item1 আর item2 আলাদা type হতে পারে (দু'জনই Summary):
pub fn notify(item1: &impl Summary, item2: &impl Summary) {}

// item1 আর item2 একই concrete type হতে হবে:
pub fn notify<T: Summary>(item1: &T, item2: &T) {}

Multiple trait bound — +

pub fn notify(item: &(impl Summary + Display)) {
    println!("Breaking news! {}", item.summarize());
}

// বা trait bound form:
pub fn notify<T: Summary + Display>(item: &T) {
    println!("Breaking news! {}", item.summarize());
    println!("Item: {}", item);
}

+ দিয়ে একাধিক trait combine — দুটোই থাকতে হবে।

where clause — পরিষ্কার syntax

একাধিক generic + একাধিক bound হলে inline সংস্করণ গাদাগাদি:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    unimplemented!()
}

where দিয়ে পরিষ্কার:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    unimplemented!()
}

Function name, parameter, return type পাশাপাশি — পড়তে অনেক সহজ।

Return type-এ impl Trait

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

Caller-কে concrete type জানাতে হবে না, শুধু trait জানলেই হলো। Closure আর iterator-এ এর use case সবচেয়ে বেশি (Chapter 13)।

সীমাবদ্ধতা — শুধু একটাই concrete type return করা যাবে। নিচের code কাজ করবে না:

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle { /* ... */ }
    } else {
        SocialPost { /* ... */ }
    }
}

Compile error — কারণ impl Summary single concrete type expect করে। দু'রকম type return করতে চাইলে trait object (Chapter 18) দরকার।

Conditional method — bound-যুক্ত impl

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

Pair<T> সবসময় new পায়। কিন্তু cmp_display শুধু সেইসব T-এ থাকবে যাদের Display ও PartialOrd আছে।

Blanket implementation

Trait-কে শর্তসহ যেকোনো type-এ implement করা যায় — একে বলে blanket impl। Standard library প্রচুর use করে। যেমন:

impl<T: Display> ToString for T {
    // ...
}

এই blanket-এর জন্য — যেকোনো type যা Display implement করে, সেটায় .to_string() available। তাই 3.to_string() কাজ করে — i32-এ Display আছে।

এই পাঠ থেকে যা শিখলে

  • trait Name { ... } দিয়ে define; impl Trait for Type দিয়ে implement।
  • Default implementation দেওয়া যায়; trait-এর অন্য method-ও call করতে পারে।
  • Orphan rule — trait বা type, একটা local হতে হবে।
  • impl Trait argument/return-এ; trait bound <T: Trait>; + multiple bound; where clause clean।
  • Conditional impl — impl<T: Bound> Type<T> দিয়ে শর্তসাপেক্ষ method।
  • Blanket impl — generic-যুক্ত impl যেকোনো T-এ apply।