اجرای کد هنگام پاکسازی با ویژگی Drop

ویژگی دوم که برای الگوی اشاره‌گر (Pointer) هوشمند مهم است، Drop است که به شما امکان می‌دهد سفارشی کنید که وقتی یک مقدار قرار است از دامنه خارج شود، چه اتفاقی بیفتد. می‌توانید یک پیاده‌سازی برای ویژگی Drop روی هر نوعی ارائه دهید و این کد می‌تواند برای آزادسازی منابعی مانند فایل‌ها یا اتصالات شبکه استفاده شود.

ما ویژگی Drop را در زمینه اشاره‌گر (Pointer)های هوشمند معرفی می‌کنیم زیرا عملکرد ویژگی Drop تقریباً همیشه هنگام پیاده‌سازی یک اشاره‌گر (Pointer) هوشمند استفاده می‌شود. برای مثال، وقتی یک Box<T> حذف می‌شود، فضای موجود روی پشته‌ای که باکس به آن اشاره می‌کند، آزاد خواهد شد.

در برخی زبان‌ها، برای برخی از انواع، برنامه‌نویس باید کدی را فراخوانی کند تا حافظه یا منابع را هر بار که استفاده از یک نمونه از این انواع به پایان رسید، آزاد کند. مثال‌ها شامل دسته‌های فایل، سوکت‌ها یا قفل‌ها می‌باشند. اگر فراموش کنند، ممکن است سیستم بیش از حد بارگذاری شود و خراب شود. در Rust، می‌توانید مشخص کنید که بخشی از کد خاصی هر زمان که یک مقدار از دامنه خارج شد، اجرا شود و کامپایلر این کد را به صورت خودکار درج خواهد کرد. در نتیجه، نیازی نیست که در مورد قرار دادن کد پاکسازی در همه جاهای برنامه‌ای که استفاده از یک نمونه خاص به پایان رسیده است، مراقب باشید—شما همچنان منابع را نشت نخواهید داد!

شما کدی که باید هنگام خروج مقدار از دامنه اجرا شود را با پیاده‌سازی ویژگی Drop مشخص می‌کنید. ویژگی Drop نیازمند این است که یک متد به نام drop را پیاده‌سازی کنید که یک مرجع متغیر به self می‌گیرد. برای دیدن زمانی که Rust فراخوانی drop را انجام می‌دهد، بیایید drop را با جملات println! برای اکنون پیاده‌سازی کنیم.

فهرست 15-14 یک ساختار CustomSmartPointer را نشان می‌دهد که تنها قابلیت سفارشی آن این است که وقتی نمونه‌ای از آن از دامنه خارج می‌شود، Dropping CustomSmartPointer! را چاپ می‌کند تا نشان دهد که چه زمانی Rust متد drop را اجرا می‌کند.

Filename: 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.");
}
Listing 15-14: ساختار CustomSmartPointer که ویژگی Drop را پیاده‌سازی می‌کند و در آن کد پاکسازی خود را قرار می‌دهیم

ویژگی Drop در پیش‌درآمد (prelude) گنجانده شده است، بنابراین نیازی به وارد کردن آن به دامنه نداریم. ما ویژگی Drop را روی CustomSmartPointer پیاده‌سازی می‌کنیم و یک پیاده‌سازی برای متد drop ارائه می‌دهیم که println! را فراخوانی می‌کند. بدنه تابع drop جایی است که هر منطقی که بخواهید هنگام خروج یک نمونه از نوع شما از دامنه اجرا شود، قرار می‌دهید. ما در اینجا متنی را چاپ می‌کنیم تا به صورت بصری نشان دهیم که چه زمانی Rust متد drop را فراخوانی خواهد کرد.

در تابع main، دو نمونه از CustomSmartPointer ایجاد می‌کنیم و سپس CustomSmartPointers created را چاپ می‌کنیم. در پایان main، نمونه‌های ما از CustomSmartPointer از دامنه خارج خواهند شد و Rust کدی که در متد drop قرار داده‌ایم را فراخوانی خواهد کرد و پیام نهایی ما را چاپ می‌کند. توجه کنید که نیازی به فراخوانی صریح متد drop نداشتیم.

وقتی این برنامه را اجرا می‌کنیم، خروجی زیر را مشاهده خواهیم کرد:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running `target/debug/drop-example`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

Rust به صورت خودکار drop را برای ما فراخوانی کرد وقتی که نمونه‌های ما از دامنه خارج شدند و کدی که مشخص کرده بودیم را اجرا کرد. متغیرها به ترتیب معکوس ایجادشان حذف می‌شوند، بنابراین d قبل از c حذف شد. هدف این مثال این است که یک راهنمای بصری برای نحوه کارکرد متد drop به شما بدهد؛ معمولاً شما کد پاکسازی که نوع شما نیاز دارد را مشخص می‌کنید نه یک پیام چاپ.

حذف زودهنگام یک مقدار با استفاده از std::mem::drop

متأسفانه، غیرفعال کردن عملکرد خودکار drop ساده نیست. در اغلب موارد، نیازی به غیرفعال کردن drop نیست؛ هدف اصلی ویژگی Drop این است که این کار به‌طور خودکار انجام شود. با این حال، گاهی ممکن است بخواهید یک مقدار را زودتر از زمان خود تمیز کنید. یک مثال در این زمینه، استفاده از اشاره‌گر (Pointer)های هوشمندی است که قفل‌ها را مدیریت می‌کنند: ممکن است بخواهید متد drop که قفل را آزاد می‌کند را به زور اجرا کنید تا کد دیگری در همان حوزه بتواند قفل را بدست آورد.
Rust به شما اجازه نمی‌دهد متد drop متعلق به ویژگی Drop را به صورت دستی فراخوانی کنید؛ در عوض، باید از تابع std::mem::drop که توسط کتابخانه استاندارد فراهم شده است، استفاده کنید اگر می‌خواهید مقداری را زودتر از زمان معمول حذف کنید.

اگر بخواهیم متد drop مربوط به ویژگی Drop را به صورت دستی فراخوانی کنیم و تابع main را از مثال شماره 15-14 تغییر دهیم، همان‌طور که در لیست 15-15 نشان داده شده است، با خطای کامپایل مواجه خواهیم شد:

Filename: 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("some data"),
    };
    println!("CustomSmartPointer created.");
    c.drop();
    println!("CustomSmartPointer dropped before the end of main.");
}
Listing 15-15: تلاش برای فراخوانی دستی متد drop از ویژگی Drop برای پاکسازی زودهنگام

وقتی سعی کنیم این کد را کامپایل کنیم، با این خطا مواجه می‌شویم:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
  --> src/main.rs:16:7
   |
16 |     c.drop();
   |       ^^^^ explicit destructor calls not allowed
   |
help: consider using `drop` function
   |
16 |     drop(c);
   |     +++++ ~

For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error

این پیام خطا نشان می‌دهد که ما اجازه نداریم به‌طور صریح drop را فراخوانی کنیم. پیام خطا از اصطلاح تخریب‌گر (Destructor) استفاده می‌کند که اصطلاحی کلی برای تابعی است که یک نمونه را تمیز می‌کند. یک تخریب‌گر مشابه یک سازنده (Constructor) است که یک نمونه را ایجاد می‌کند. تابع drop در Rust یک تخریب‌گر خاص است.

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

ما نمی‌توانیم قرار دادن خودکار drop را هنگام خروج یک مقدار از حوزه غیرفعال کنیم و همچنین نمی‌توانیم متد drop را به صورت صریح فراخوانی کنیم. بنابراین، اگر نیاز به حذف زودهنگام یک مقدار داشته باشیم، باید از تابع std::mem::drop استفاده کنیم.

تابع std::mem::drop با متد drop در ویژگی Drop متفاوت است. این تابع را با ارسال مقداری که می‌خواهیم به‌زور حذف کنیم به‌عنوان آرگومان فراخوانی می‌کنیم. این تابع در پیش‌فرض (Prelude) قرار دارد، بنابراین می‌توانیم تابع main را در لیست 15-15 تغییر دهیم تا تابع drop را فراخوانی کند، همان‌طور که در لیست 15-16 نشان داده شده است:

Filename: 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("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}
Listing 15-16: فراخوانی std::mem::drop برای حذف صریح یک مقدار قبل از خروج آن از حوزه

اجرای این کد خروجی زیر را چاپ خواهد کرد:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
     Running `target/debug/drop-example`
CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.

متن Dropping CustomSmartPointer with data 'some data'! بین متون CustomSmartPointer created. و CustomSmartPointer dropped before the end of main. چاپ می‌شود و نشان می‌دهد که کد متد drop برای حذف c در آن نقطه فراخوانی شده است.

شما می‌توانید از کدی که در پیاده‌سازی ویژگی Drop مشخص کرده‌اید، به روش‌های مختلفی برای ساده و امن کردن عملیات پاکسازی استفاده کنید: برای مثال، می‌توانید از آن برای ایجاد تخصیص‌دهنده حافظه خودتان استفاده کنید! با ویژگی Drop و سیستم مالکیت Rust، نیازی به یادآوری پاکسازی ندارید، زیرا Rust این کار را به‌طور خودکار انجام می‌دهد.

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

اکنون که Box<T> و برخی از ویژگی‌های اشاره‌گر (Pointer)های هوشمند را بررسی کردیم، بیایید به چند اشاره‌گر (Pointer) هوشمند دیگر که در کتابخانه استاندارد تعریف شده‌اند، نگاهی بیندازیم.