পাঠ ১৫.২

Smart Pointer-কে regular reference-এর মতো behave করানো

Treating Smart Pointers Like Regular References

Deref trait implement করলে তোমার type-এ dereference operator *-এর behavior customize করা যায় (multiplication-এর * না)। এর ফলে smart pointer-গুলো regular reference-এর মতোই code-এ ব্যবহার করা যায় — যেসব function reference নেয়, সেখানেও smart pointer কাজ করে।

Reference follow করা

Regular reference একটা ধরনের pointer — address-এ কী আছে সেটা পেতে dereference operator * দিয়ে follow করতে হয়।

fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

y হলো x-এর reference। সরাসরি assert_eq!(5, y) লিখলে type mismatch — number এবং reference compare করা যায় না। *y দিয়ে আগে value নিতে হয়।

Box-কে reference-এর মতো use

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

আগের code-এর সাথে শুধু একটাই তফাত — y এখন reference না, একটা Box। কিন্তু *y একইভাবে কাজ করে। Box heap-এ data রাখে, reference borrow করে — দু'জনের সাথেই * একইভাবে use হয়।

নিজেদের smart pointer

নিজেরা একটা MyBox<T> বানাব — Box-এর মতোই কিন্তু simplified।

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

fn main() {}

Tuple struct — শুধু একটাই element। new সেই element wrap করে।

এবার reference-এর মতো use করার চেষ্টা:

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

Compile হবে না — MyBox Deref implement করে না, তাই Rust জানে না *y-এর meaning কী।

Deref trait implement

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

type Target = T; — associated type; trait-এর জন্য একটা generic parameter declare করার একটু ভিন্ন পদ্ধতি।

deref(&self) -> &Self::Target — inner data-র reference return করে। &self.0 মানে tuple struct-এর প্রথম (এবং একমাত্র) field-এর reference।

Behind the scenes — *y Rust translate করে নেয় *(y.deref())-এ। Rust automatic deref() call করে reference পায়, তারপর সেই reference-কে regular * দিয়ে dereference।

deref reference return করে — value না। কেন? কারণ value return করলে ownership move হত — অধিকাংশ ক্ষেত্রে আমরা সেটা চাই না, smart pointer-এর ভেতরের data-টা যাতে রয়ে যায়।

Deref coercion

Deref coercion — Rust-এর একটা সুবিধা। যে type Deref implement করে, তার reference Rust automatically সেই type-এর target-এর reference-এ রূপান্তর করতে পারে। মানে — &MyBox<String> থেকে &String থেকে &str — automatic chain।

এটা function/method call-এ সবচেয়ে দরকারে আসে। উদাহরণ — একটা hello function যেটা &str নেয়:

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {}

এখন MyBox<String>-এর reference pass করতে চাই:

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

&m &MyBox<String>। Rust deref call করে &String বানায়; standard library-তে String-এর Deref implementation আছে যেটা &str দেয়। তাই pass হয়ে যায়।

Deref coercion না থাকলে কেমন হত:

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&(*m)[..]);
}

(*m) — MyBox dereference করে String; [..] — পুরো String-এর slice। Deref coercion এই বাড়তি ceremony বাঁচায় — code পড়তে এবং লিখতে দু'টোই সহজ।

Coercion-এর resolution compile time-এ হয় — runtime cost নেই।

Mutable reference-এ Deref

Mutable reference-এ * override করতে হলে DerefMut trait।

Rust তিন case-এ deref coercion করে:

  1. &T থেকে &U যখন T: Deref<Target=U>
  2. &mut T থেকে &mut U যখন T: DerefMut<Target=U>
  3. &mut T থেকে &U যখন T: Deref<Target=U>

Mutable থেকে immutable-এ coerce safe — borrowing rule অনুযায়ী mutable reference unique, তাকে immutable হিসেবে treat করলে rule ভাঙে না। কিন্তু উল্টো — immutable থেকে mutable — possible না, কারণ সেই data-এ অন্য immutable reference থাকতে পারে।

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

  • Deref trait — type-এ *-এর behavior define করে।
  • *y Rust চালায় হিসেবে *(y.deref())
  • deref reference return করে — ownership move হয় না।
  • Deref coercion — function/method call-এ Rust automatic chain করে; &MyBox<String>&String&str
  • Mutable reference-এ DerefMut; coercion mutable → immutable safe, উল্টো না।