مقدمه
در درس قبل با Box<T> به عنوان یک اشارهگر هوشمند ساده آشنا
شدیم. یکی از ویژگیهای کلیدی که به
یک struct اجازه میدهد تا مانند یک اشارهگر رفتار کند، پیادهسازی Deref Trait است.
این
trait به ما اجازه میدهد تا رفتار عملگر dereference (*) را
برای نوع خودمان سفارشی کنیم.
با پیادهسازی Deref، میتوانیم کدی بنویسیم که همزمان هم مزایای یک struct (داشتن
فیلدها و
متدهای اختصاصی) و هم راحتی کار با اشارهگرها را داشته باشد.
عملگر Dereference و دسترسی به مقدار
در Rust، عملگر * برای dereference کردن یا "دنبال کردن" یک رفرنس یا اشارهگر برای دسترسی
به
مقدار واقعی آن استفاده میشود.
src/main.rs
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
ما نمیتوانیم x و y را مستقیماً مقایسه کنیم، زیرا آنها از نوعهای متفاوتی هستند
(i32 در
مقابل &i32). برای دسترسی به مقدار عددی که y به آن اشاره
میکند، باید از عملگر * استفاده
کنیم. اشارهگر هوشمند Box<T> نیز به همین شکل کار میکند.
پیادهسازی Deref Trait
بیایید یک نوع اشارهگر هوشمند سفارشی به نام MyBox<T> بسازیم
تا ببینیم Deref Trait چگونه کار
میکند.
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);
}
برای پیادهسازی Deref Trait، باید یک نوع وابسته به نام Target و یک متد به نام deref() را تعریف کنیم. متد deref یک رفرنس تغییرناپذیر به
self
قرض گرفته و یک رفرنس به داده داخلی برمیگرداند. با این پیادهسازی، Rust میداند که وقتی ما از
عملگر * روی یک مقدار از نوع MyBox<T> استفاده میکنیم،
باید در پشت صحنه متد deref را
فراخوانی کند.
تبدیل نوع ضمنی با Deref Coercion
Deref Coercion یک ویژگی بسیار کاربردی در Rust است که با Traitهایی مانند
Deref کار میکند.
این ویژگی به ما اجازه میدهد تا یک رفرنس به یک نوع که Deref Trait را پیادهسازی کرده، به
عنوان
آرگومان به تابعی پاس دهیم که یک رفرنس به نوع داخلی آن را انتظار دارد. این تبدیل به صورت خودکار
توسط کامپایلر انجام میشود.
src/main.rs
fn hello(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
در این مثال، تابع hello یک اسلایس رشتهای (&str) را به عنوان
پارامتر میپذیرد. ما یک رفرنس
به MyBox<String> (یعنی &m) را به
آن پاس میدهیم. این کد به دلیل Deref Coercion کار میکند.
کامپایلر Rust این زنجیره از تبدیلها را به صورت خودکار انجام میدهد:
- Rust میبیند که &MyBox<String> با &str مطابقت ندارد.
- متوجه میشود که MyBox<T> تریت Deref را پیادهسازی
کرده و میتواند &MyBox<String> را به
&String تبدیل کند.
- سپس، متوجه میشود که کتابخانه استاندارد نیز Deref را برای String پیادهسازی
کرده و
میتواند &String را به &str تبدیل
کند.
این تبدیلهای خودکار باعث میشوند که کدی که مینویسیم بسیار تمیزتر و سادهتر باشد، بدون اینکه
نیاز به فراخوانیهای صریح deref یا اپراتورهای * و & داشته باشیم.
در این درس با Deref Trait و نقش آن در سفارشیسازی رفتار اشارهگرها آشنا شدیم. همچنین دیدیم که
چگونه Deref Coercion به صورت خودکار رفرنسها را به نوعهای داخلیتر تبدیل کرده و کدنویسی را
سادهتر میکند. در درس بعدی، به بررسی Drop Trait خواهیم پرداخت و یاد میگیریم که چگونه کدی را
تعریف کنیم که در زمان خارج شدن یک مقدار از حوزه و آزاد شدن حافظه آن، اجرا شود.