اجرای کد هنگام پاکسازی با ویژگی 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) هوشمند دیگر که در کتابخانه
استاندارد تعریف شدهاند، نگاهی بیندازیم.