اجرای کد هنگام پاکسازی با ویژگی 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
را اجرا
میکند.
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
را پیادهسازی میکند و در آن کد پاکسازی خود را قرار میدهیمویژگی 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 نشان داده شده است، با خطای کامپایل مواجه خواهیم شد:
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
در ویژگی Drop
متفاوت است. این تابع را با ارسال مقداری که میخواهیم بهزور
حذف کنیم بهعنوان آرگومان فراخوانی میکنیم. این تابع در پیشفرض (Prelude) قرار دارد، بنابراین میتوانیم تابع main
را در لیست 15-15 تغییر دهیم تا تابع 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) هوشمند دیگر که در کتابخانه
استاندارد تعریف شدهاند، نگاهی بیندازیم.