مقدمه

در درس قبل با Box<T> به عنوان یک اشاره‌گر هوشمند ساده آشنا شدیم. یکی از ویژگی‌های کلیدی که به یک struct اجازه می‌دهد تا مانند یک اشاره‌گر رفتار کند، پیاده‌سازی Deref Trait است. این trait به ما اجازه می‌دهد تا رفتار عملگر dereference (*) را برای نوع خودمان سفارشی کنیم. با پیاده‌سازی Deref، می‌توانیم کدی بنویسیم که همزمان هم مزایای یک struct (داشتن فیلدها و متدهای اختصاصی) و هم راحتی کار با اشاره‌گرها را داشته باشد.

عملگر Dereference و دسترسی به مقدار

در Rust، عملگر * برای dereference کردن یا "دنبال کردن" یک رفرنس یا اشاره‌گر برای دسترسی به مقدار واقعی آن استفاده می‌شود.

Copy Icon src/main.rs
fn main() {
    let x = 5;
    let y = &x; // y is a reference to x

    assert_eq!(5, x);
    assert_eq!(5, *y); // We use * to follow the reference and get the value
}

ما نمی‌توانیم x و y را مستقیماً مقایسه کنیم، زیرا آنها از نوع‌های متفاوتی هستند (i32 در مقابل &i32). برای دسترسی به مقدار عددی که y به آن اشاره می‌کند، باید از عملگر * استفاده کنیم. اشاره‌گر هوشمند Box<T> نیز به همین شکل کار می‌کند.

پیاده‌سازی Deref Trait

بیایید یک نوع اشاره‌گر هوشمند سفارشی به نام MyBox<T> بسازیم تا ببینیم Deref Trait چگونه کار می‌کند.

Copy Icon src/main.rs
use std::ops::Deref;

struct MyBox<T>(T);

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

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

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

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

    assert_eq!(5, x);
    assert_eq!(5, *y); // This works because we implemented Deref!
}

برای پیاده‌سازی Deref Trait، باید یک نوع وابسته به نام Target و یک متد به نام deref() را تعریف کنیم. متد deref یک رفرنس تغییرناپذیر به self قرض گرفته و یک رفرنس به داده داخلی برمی‌گرداند. با این پیاده‌سازی، Rust می‌داند که وقتی ما از عملگر * روی یک مقدار از نوع MyBox<T> استفاده می‌کنیم، باید در پشت صحنه متد deref را فراخوانی کند.

تبدیل نوع ضمنی با Deref Coercion

Deref Coercion یک ویژگی بسیار کاربردی در Rust است که با Traitهایی مانند Deref کار می‌کند. این ویژگی به ما اجازه می‌دهد تا یک رفرنس به یک نوع که Deref Trait را پیاده‌سازی کرده، به عنوان آرگومان به تابعی پاس دهیم که یک رفرنس به نوع داخلی آن را انتظار دارد. این تبدیل به صورت خودکار توسط کامپایلر انجام می‌شود.

Copy Icon src/main.rs
fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m); // Deref Coercion in action!
}

در این مثال، تابع hello یک اسلایس رشته‌ای (&str) را به عنوان پارامتر می‌پذیرد. ما یک رفرنس به MyBox<String> (یعنی &m) را به آن پاس می‌دهیم. این کد به دلیل Deref Coercion کار می‌کند. کامپایلر Rust این زنجیره از تبدیل‌ها را به صورت خودکار انجام می‌دهد:

  1. Rust می‌بیند که &MyBox<String> با &str مطابقت ندارد.
  2. متوجه می‌شود که MyBox<T> تریت Deref را پیاده‌سازی کرده و می‌تواند &MyBox<String> را به &String تبدیل کند.
  3. سپس، متوجه می‌شود که کتابخانه استاندارد نیز Deref را برای String پیاده‌سازی کرده و می‌تواند &String را به &str تبدیل کند.

این تبدیل‌های خودکار باعث می‌شوند که کدی که می‌نویسیم بسیار تمیزتر و ساده‌تر باشد، بدون اینکه نیاز به فراخوانی‌های صریح deref یا اپراتورهای * و & داشته باشیم.

در این درس با Deref Trait و نقش آن در سفارشی‌سازی رفتار اشاره‌گرها آشنا شدیم. همچنین دیدیم که چگونه Deref Coercion به صورت خودکار رفرنس‌ها را به نوع‌های داخلی‌تر تبدیل کرده و کدنویسی را ساده‌تر می‌کند. در درس بعدی، به بررسی Drop Trait خواهیم پرداخت و یاد می‌گیریم که چگونه کدی را تعریف کنیم که در زمان خارج شدن یک مقدار از حوزه و آزاد شدن حافظه آن، اجرا شود.