পাঠ ২০.৩

Advanced Type

Advanced Types

Rust-এর type system-এ আরও কিছু দরকারি ধারণা আছে — newtype pattern, type alias, never type (!), এবং dynamically sized type। সব মিলিয়ে complex API ও safe abstraction গড়ার যন্ত্রপাতি।

Newtype pattern — type safety

Newtype pattern শুধু orphan rule এড়াতে না — type confusion প্রতিরোধেও কাজ করে। যেমন Millimeters(u32)Meters(u32) — দু'টোই u32 wrap, কিন্তু compile time-এ আলাদা type। ভুল করে mix করলে compile error।

আরেকটা ব্যবহার — internal implementation hide। যেমন People type HashMap<i32, String> wrap করে public API দেয় — user HashMap-এর existence জানতেও পারে না।

Type alias — type keyword

type দিয়ে একটা type-এর synonym তৈরি:

fn main() {
    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;

    println!("x + y = {}", x + y);
}

লক্ষ করো — Kilometers আলাদা type না, শুধু i32-এর synonym। তাই type-mix-এ compiler কিছু বলবে না। এই দিক থেকে newtype pattern-এর চেয়ে দুর্বল।

কখন alias দরকার

লম্বা type বারবার লেখা থেকে বাঁচাতে:

fn main() {
    let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));

    fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
        // --snip--
    }

    fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
        // --snip--
        Box::new(|| ())
    }
}

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

fn main() {
    type Thunk = Box<dyn Fn() + Send + 'static>;

    let f: Thunk = Box::new(|| println!("hi"));

    fn takes_long_type(f: Thunk) {
        // --snip--
    }

    fn returns_long_type() -> Thunk {
        // --snip--
        Box::new(|| ())
    }
}

Result alias — std::io-র অভ্যাস

use std::fmt;

type Result<T> = std::result::Result<T, std::io::Error>;

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;

    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}

সবগুলো method-এ std::io::Error বার বার লেখার বদলে শুধু Result<T>

Never type — !

! = empty type, never type। কোনো value নেই — এমন function-এর return type যা কখনো return করে না:

fn bar() -> ! {
    // --snip--
    panic!();
}

এ ধরনের function-কে বলে diverging function

!-এর বাস্তব ব্যবহার

Match-এর সব arm-এর type একই হতে হবে — কিন্তু continue, panic!,break, infinite loop সবগুলোর type !। এটা যেকোনো type-এ coerce হতে পারে। Guessing game থেকে:

fn main() {
    let guess = "3";
    let guess = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => continue,
    };
}

এক arm u32, আরেক arm continue — ! coerce হয়ে u32 হয়ে যায়, conflict নেই।

unwrap-এর মধ্যেও একই কাজ:

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

use crate::Option::*;

impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
}

panic!-এর type ! — তাই overall match T return করতে পারে।

Infinite loop:

fn main() {
    print!("forever ");

    loop {
        print!("and ever ");
    }
}

break ছাড়া infinite loop-এর type !

Dynamically Sized Type এবং Sized

Rust সাধারণত compile time-এ সব type-এর size জানে। কিন্তু কিছু type-এর size শুধু runtime-এ — এদের বলে DST বা unsized type

সবচেয়ে চেনা — str। সরাসরি variable বানানো যায় না:

fn main() {
    let s1: str = "Hello there!";
    let s2: str = "How's it going?";
}

কারণ s1 = 12 byte, s2 = 15 byte — same type-এর সব value একই size হতে হবে। সমাধান — pointer-এর পেছনে রাখা: &str। যা দু'টা value ধরে রাখে — pointer + length। তাই compile time-এ size জানা — সবসময় 2 * usize

DST-এর golden rule

DST-এর value সবসময় কোনো না কোনো pointer-এর পেছনে থাকতে হবে। যেমন:

  • &str, Box<str>, Rc<str>
  • Trait-এ — &dyn Trait, Box<dyn Trait>, Rc<dyn Trait>

Sized trait

Compile time-এ size জানা type-গুলো auto Sized

গোপন বিষয় — Rust প্রতিটা generic function-এ implicit Sized bound যোগ করে:

fn generic<T>(t: T) {
    // --snip--
}

আসলে এটা treat হয়:

fn generic<T: Sized>(t: T) {
    // --snip--
}

DST allow করতে চাইলে ?Sized bound:

fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

মানে — "T sized হতে পারে, না-ও হতে পারে"। যেহেতু possibly unsized, parameter-কে pointer-এর পেছনে রাখতে হয় (এখানে &T)। ?Sized শুধু Sized-এর জন্যই available।

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

  • Newtype — type confusion প্রতিরোধ ও implementation hide; runtime cost শূন্য।
  • type alias — synonym, আলাদা type না; লম্বা type-নাম পরিষ্কার রাখার জন্য।
  • Never type ! — diverging function; panic!, continue, infinite loop-এর type।
  • DST — runtime-এ size; pointer-এর পেছনে থাকতে হবে (যেমন &str, Box<dyn Trait>)।
  • Generic-এ implicit Sized bound; ?Sized দিয়ে relax করা যায়।