পাঠ ১০.১

Generic data type

Generic Data Types

Generic দিয়ে আমরা এমন code লিখি যা একাধিক type-এ কাজ করে — duplicate না করে। Function, struct, enum, method — সবেই generic ব্যবহার করা যায়।

Function-এ generic

ধরো আমাদের একটা list-এর largest element বের করতে হবে। প্রথমে i32-এর জন্য:

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

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

    largest
}

তারপর char-এর জন্য — প্রায় হুবহু same code:

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

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

    largest
}

Code duplication! Generic দিয়ে এক function-এ সমাধান। Type parameter angle bracket-এ:

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

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

    largest
}

এই code এখনই compile হবে না। কারণ > operator যেকোনো type-এ কাজ করে না। Compiler বলবে — "T-এর সব value-র জন্য > defined না"।

Trait bound

T-কে restrict করতে হবে — শুধু এমন type যেগুলোতে comparison সম্ভব। PartialOrd trait এই capability define করে:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

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

    largest
}

T: PartialOrd মানে — "T এমন type হতে হবে যা PartialOrd implement করে"। i32 এবং char দুটোই করে, তাই ঠিক চলে। Trait নিয়ে বিস্তারিত পরের পাঠে।

Struct-এ generic

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

লক্ষ্য — দু'টো field একই type T। তাই এটা compile হবে না:

fn main() {
    let wont_work = Point { x: 5, y: 4.0 };
}
compile errortext
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

আলাদা type-ও support করতে চাইলে — আলাদা generic parameter:

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

Generic parameter খুব বেশি (যেমন ৫টা) হলে code পড়া কঠিন — তখন struct restructure করার কথা ভাবো।

Enum-এ generic

Option<T> এবং Result<T, E> generic enum-এর প্রসিদ্ধ example — আমরা আগে use করেছি:

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Method-এ generic

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

লক্ষ্য — impl<T>impl-এর পরে <T> declare করতে হয়, না হলে Rust জানে না এটা generic না কোনো concrete type।

Specific type-এ method

কিছু method শুধু concrete instantiation-এই থাকবে — যেমন distance_from_origin শুধু Point<f32>-এ:

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

impl<T> না — সরাসরি impl Point<f32>। তাই Point<i32>-এ এই method থাকবে না।

impl ও method-এ আলাদা generic

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);
    // p3.x = 5, p3.y = c
}

X1, Y1 struct-এর সাথে যুক্ত (impl-এ declare); X2, Y2 শুধু এই method-এর জন্য (fn mixup-এ declare)। Result-এ X1 (self থেকে) এবং Y2 (other থেকে) — mix।

Performance — কোনো cost নেই

C++-এ template একটা cost বা restriction-এর সাথে আসে। Rust-এ generic-এর runtime cost শূন্য — কারণ monomorphization। Compile time-এ Rust deduce করে কোন কোন concrete type use হচ্ছে এবং প্রতিটার জন্য আলাদা code generate করে। উদাহরণ:

let integer = Some(5);
let float = Some(5.0);

Compiler কার্যত generate করে:

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);

Runtime-এ কোনো type-checking বা dispatch দরকার নেই। Manual-ে আলাদা আলাদা type লেখার মতোই fast — কিন্তু source code-এ duplicate-হীন।

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

  • Generic — type parameter angle bracket-এ; function, struct, enum, method-এ।
  • T: TraitName trait bound দিয়ে restriction।
  • impl<T> generic; specific instantiation-এ impl Point<f32>
  • Struct-এর generic এবং method-এর generic আলাদা হতে পারে।
  • Monomorphization — compile-time-এ concrete code generation, runtime cost শূন্য।