اجرای کد هنگام پاکسازی با ویژگی Drop
ویژگی دوم که برای الگوی اشارهگر (Pointer) هوشمند مهم است، Drop است که به شما امکان میدهد سفارشی کنید که وقتی یک مقدار
قرار است از دامنه خارج شود، چه اتفاقی بیفتد. میتوانید یک پیادهسازی برای ویژگی Drop روی هر نوعی ارائه دهید و
این کد میتواند برای آزادسازی منابعی مانند فایلها یا اتصالات شبکه استفاده شود.
ما ویژگی Drop را در زمینه اشارهگر (Pointer)های هوشمند معرفی میکنیم زیرا عملکرد ویژگی Drop تقریباً همیشه هنگام
پیادهسازی یک اشارهگر (Pointer) هوشمند استفاده میشود. برای مثال، وقتی یک Box<T> حذف میشود، فضای موجود روی پشتهای
که باکس به آن اشاره میکند، آزاد خواهد شد.
در برخی زبانها، برای برخی نوعها، برنامهنویس باید هر بار که استفاده از یک نمونه از آن نوعها تمام میشود، کدی را برای آزادسازی حافظه یا منابع اجرا کند. نمونههایی از این نوع شامل فایل هندلها، سوکتها و لاکها هستند. اگر برنامهنویس این کار را فراموش کند، ممکن است سیستم دچار بار اضافی شده و از کار بیفتد. در Rust، میتوانید مشخص کنید که قطعه کد خاصی هنگام خارج شدن یک مقدار از حوزهی دید (scope) اجرا شود، و کامپایلر این کد را بهصورت خودکار درج خواهد کرد. در نتیجه، نیازی نیست نگران این باشید که در تمام بخشهای برنامه، کد پاکسازی (cleanup) را درج کنید؛ حتی با این وجود نیز دچار نشت منابع نخواهید شد!
شما کدی که باید هنگام خروج مقدار از دامنه اجرا شود را با پیادهسازی ویژگی Drop مشخص میکنید. ویژگی Drop
نیازمند این است که یک متد به نام drop را پیادهسازی کنید که یک مرجع متغیر به self میگیرد. برای دیدن زمانی
که Rust فراخوانی drop را انجام میدهد، بیایید drop را با جملات println! برای اکنون پیادهسازی کنیم.
لیستینگ 15-14 یک struct بهنام CustomSmartPointer را نشان میدهد که تنها عملکرد سفارشی آن این است که هنگام خارج شدن نمونه از حوزهی دید (scope)، پیام Dropping CustomSmartPointer! را چاپ میکند تا نشان دهد چه زمانی Rust متد drop را اجرا میکند.
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."); }
CustomSmartPointer که ویژگی Drop را پیادهسازی میکند و در آن کد پاکسازی خود را قرار میدهیمtrait مربوط به Drop در prelude زبان Rust گنجانده شده است، بنابراین نیازی نیست آن را بهطور جداگانه به حوزهی دید (scope) وارد کنیم. ما trait Drop را برای CustomSmartPointer پیادهسازی کردهایم و برای متد drop یک پیادهسازی ارائه دادهایم که در آن از println! استفاده میشود. بدنهی متد drop جایی است که میتوانید هر منطقی را که میخواهید هنگام خارج شدن یک نمونه از نوعتان از scope اجرا شود، قرار دهید. ما در اینجا صرفاً با چاپ یک متن، بهصورت بصری نشان میدهیم که 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 به شما بدهد؛ معمولاً شما کد پاکسازی که نوع شما نیاز دارد را مشخص
میکنید نه یک پیام چاپ.
متأسفانه غیرفعالکردن عملکرد خودکار drop کار سادهای نیست. در اغلب موارد نیز نیازی به غیرفعالکردن آن نیست؛ تمام هدف trait مربوط به Drop این است که فرآیند پاکسازی بهطور خودکار مدیریت شود. با این حال، گاهی ممکن است بخواهید یک مقدار را زودتر از زمان معمول پاکسازی کنید. یکی از نمونهها زمانی است که از smart pointerهایی استفاده میکنید که قفلها (locks) را مدیریت میکنند: ممکن است بخواهید متد drop که قفل را آزاد میکند را بهصورت دستی فراخوانی کنید تا سایر کدهای همان scope بتوانند قفل را در اختیار بگیرند.
Rust اجازه نمیدهد متد drop مربوط به trait Drop را بهصورت دستی فراخوانی کنید؛ در عوض، اگر میخواهید یک مقدار را قبل از پایان حوزهی دیدش پاکسازی کنید، باید از تابع std::mem::drop که در کتابخانهی استاندارد فراهم شده استفاده کنید.
اگر تلاش کنیم تا متد drop مربوط به trait Drop را بهصورت دستی فراخوانی کنیم و تابع main موجود در لیستینگ 15-14 را تغییر دهیم، همانطور که در لیستینگ 15-15 نشان داده شده است، با خطای کامپایل مواجه خواهیم شد.
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.");
}
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 در trait Drop متفاوت است. این تابع را با ارسال مقداری که میخواهیم بهصورت اجباری drop شود، فراخوانی میکنیم. این تابع در prelude قرار دارد، بنابراین میتوانیم تابع main در لیستینگ 15-15 را تغییر دهیم تا بهجای فراخوانی مستقیم متد drop، تابع drop را فراخوانی کند؛ همانطور که در لیستینگ 15-16 نشان داده شده است.
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."); }
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) هوشمند دیگر که در کتابخانه
استاندارد تعریف شدهاند، نگاهی بیندازیم.