مقدمه

دومین trait مهم که در عملکرد اشاره‌گرهای هوشمند نقش کلیدی دارد، Drop Trait است. این trait به ما اجازه می‌دهد تا کدی را تعریف کنیم که به صورت خودکار، درست در لحظه‌ای که یک مقدار از حوزه (scope) خود خارج می‌شود، اجرا شود. این قابلیت برای آزادسازی منابعی مانند حافظه، فایل‌ها، یا اتصالات شبکه بسیار حیاتی است.

در بسیاری از زبان‌های برنامه‌نویسی با مدیریت حافظه دستی، برنامه‌نویس موظف است تا به صورت صریح منابع را در هر مسیر خروجی ممکن از کد، آزاد کند. این کار مستعد خطا است. در Rust، شما می‌توانید منطق پاک‌سازی را یک بار در متد drop پیاده‌سازی کنید و کامپایلر تضمین می‌کند که این منطق به صورت خودکار و در زمان مناسب اجرا خواهد شد.

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

Drop Trait تنها یک متد به نام drop() دارد که یک رفرنس تغییرپذیر به self (&mut self) را به عنوان پارامتر می‌گیرد. بیایید یک struct سفارشی با منطق drop خاص خود بسازیم تا ببینیم این trait چگونه کار می‌کند.

Copy Icon src/main.rs
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

در این کد، ما Drop Trait را برای struct خود پیاده‌سازی کرده و در متد drop، یک پیام چاپ می‌کنیم. اگر این کد را با cargo run اجرا کنید، خروجی زیر را خواهید دید:

CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
                    

Rust به صورت خودکار متد drop را زمانی که مقادیر از حوزه خارج می‌شوند، فراخوانی می‌کند. نکته جالب ترتیب اجرای آنهاست: مقادیر به ترتیب معکوس ایجادشان drop می‌شوند. یعنی ابتدا d و سپس c.

آزادسازی زودهنگام یک مقدار با std::mem::drop

گاهی اوقات ممکن است لازم باشد یک مقدار را قبل از اینکه از حوزه خود خارج شود، آزاد کنیم. Rust به ما اجازه نمی‌دهد که متد drop() را به صورت دستی فراخوانی کنیم. این یک محدودیت امنیتی است تا از خطای «آزادسازی دوگانه» (double free) جلوگیری شود، زیرا Rust همچنان به صورت خودکار drop را در انتهای حوزه فراخوانی خواهد کرد.

برای این کار، باید از تابع ارائه شده توسط کتابخانه استاندارد، یعنی std::mem::drop()، استفاده کنیم. این تابع مقدار مورد نظر را به عنوان آرگومان گرفته و مالکیت آن را به خود منتقل می‌کند و بلافاصله آن را از حافظه پاک می‌کند.

Copy Icon src/main.rs
fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    std::mem::drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}

با اجرای این کد، خواهید دید که پیام Dropping... قبل از پیام "dropped before the end" چاپ می‌شود، که نشان می‌دهد مقدار c قبل از پایان تابع main آزاد شده است.

در این درس با Drop Trait و نقش حیاتی آن در مدیریت خودکار منابع در Rust آشنا شدیم. دیدیم که چگونه می‌توان با پیاده‌سازی این trait، منطق پاک‌سازی سفارشی را تعریف کرد و چگونه Rust با فراخوانی خودکار آن، ایمنی حافظه را تضمین می‌کند. در درس بعدی، به سراغ اشاره‌گر هوشمند Rc<T> خواهیم رفت و یاد می‌گیریم که چگونه می‌توانیم ترتیبی دهیم که یک مقدار به صورت همزمان چندین مالک داشته باشد.