السمة الثانية المهمة لنمط المؤشرات الذكية smart pointer pattern هي السمة Drop
، التي تسمح لك بتخصيص ماذا يحدث إذا كانت القيمة ستخرج من النطاق scope. يمكنك تأمين تنفيذ لسمة Drop
على أي نوع، ويمكن استخدام هذه الشيفرة البرمجية لتحرير الموارد، مثل الملفات واتصالات الشبكة.
قدّمنا السمة Drop
في سياق المؤشرات الذكية، إذ تُستخدم وظيفة سمة Drop
دائمًا عند تطبيق مؤشر ذكي، ومثال على ذلك: عندما يُحرَّر Box<T>
ستحرّر المساحة المخصصة له في الكومة heap التي يشير إليها الصندوق box.
يتوجب على المبرمج في بعض لغات البرمجة ولبعض الأنواع استدعاء الشيفرة البرمجية التي تحرّر مساحة تخزين أو موارد في كل مرة ينتهي من استخدام نسخة instance من هذه الأنواع، ومن الأمثلة على ذلك مقابض الملفات file handles والمقابس sockets أو الأقفال locks، وإذا نسي المبرمجون استدعاء تلك الشيفرة البرمجية (لتحرير مساحة التخزين والموارد)، سيزداد التحميل على النظام وسيتوقف النظام عن العمل بحلول نقطة معيّنة. يمكنك في لغة رست تحديد قسم معين من الشيفرة تُنفذ عندما تخرج قيمة ما من النطاق، إذ سيضيف المصرف هذه الشيفرة تلقائيًا ونتيجةً ذلك لا تحتاج أن تكون حذرًا بخصوص وضع شيفرة تحرير المساحة البرمجية cleanup code في كل مكان في البرنامج عندما تكون نسخة من نوع معين قد انتهت، أي لن يكون هناك أي هدر في الموارد.
يمكنك تحديد الشيفرة البرمجية التي تريد تنفيذها عندما تخرج قيمة ما عن النطاق وذلك باستخدام تنفيذ سمة Drop
، إذ تحتاج سمة Drop
أن تطبّق تابع method واحد اسمه drop
يأخذ مرجعًا متغيّرًا إلى self
لينتظر استدعاء رست للدالة drop
. دعنا ننفّذ drop
مع تعليمات println!
في الوقت الحالي.
توضّح الشيفرة 15 البنية CustomSmartPointer
بوظيفة مخصّصة وحيدة هي طباعة Dropping CustomSmartPointer!
عندما تخرج النسخة عن النطاق لإظهار لحظة تنفيذ رست للخاصية drop
.
اسم الملف: 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."); }
[الشيفرة 15: بنية CustomSmartPointer
التي تنفّذ السمة Drop
عند مكان وضع شيفرة تحرير الذاكرة]
السمة Drop
مضمّنة في المقدمة لذا لا نحتاج لأن نضيفها إلى النطاق. ننفّذ سمة Drop
على CustomSmartPointer
ونقدّم تنفيذًا لتابع drop
الذي يستدعي بدوره println!
، ونضع في متن دالة drop
أي منطق نريد تنفيذه عند تخرج نسخة من النوع خارج النطاق، كما أننا نطبع نصًا هنا لنوضح كيف تستدعي رست drop
بصريًا.
أنشأنا في main
نسختين من CustomSmartPointer
ومن ثم طبعنا CutsomSmartPointers created
، سيخرج CustomSmartPointer
بنهاية main
خارج النطاق، مما يعني أن رست ستستدعي الشيفرة التي وضعناها في تابع drop
مما سيتسبب بطباعة رسالتنا النهائية، مع ملاحظة بأننا لم نستدعي التابع drop
صراحةً.
نحصل على الخرج التالي عندما ننفذ هذا البرنامج:
$ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished dev [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`!
استدعت رست drop
تلقائيًا عندما خرجت نُسخنا عن النطاق واستدعى drop
بدوره الشيفرة البرمجية التي حددناها. تُحرَّر المتغيرات عكس ترتيب انشائها لذا تُحرَّر d
قبل c
. يهدف هذا المثال إلى منحك دليلًا بصريًا مُلاحَظ لكيفية عمل التابع drop
، إذ أنك تحدّد عادةً شيفرة تحرير الذاكرة التي تحتاجها بدلاً من طباعة رسالة.
تحرير قيمة مبكرًا باستخدام دالة std::mem::drop
لسوء الحظ، ليس من السهل تعطيل خاصية drop
التلقائية، إلا أن تعطيل drop
ليس ضروريًا عادةً، إذ أن الهدف من سمة Drop
هي أنها تحدث تلقائيًا. نريد أحياناً تحرير قيمة ما مبكراً ومثال على ذلك هو استخدام المؤشرات الذكية لإدارة الأقفال، إذ قد تضطر لإجبار تابع drop
على تحرير القفل لتستطيع الشيفرة البرمجية الموجودة في النطاق ذاته الحصول عليه. لا تتيح لك راست استدعاء تابع drop
الخاص بسمة Drop
يدويًا، بل يجب عليك بدلاً من ذلك استدعاء دالة std::mem::drop
المضمّنة في المكتبة القياسية، إذا أردت تحرير قيمة قسريًا قبل أن تخرج عن نطاقها.
نحصل على خطأ تصريفي إذا أردنا استدعاء التابع drop
الخاص بسمة drop
يدويًا وذلك بتعديل دالة main
من الشيفرة 15 كما هو موضح:
اسم الملف: 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."); }
[الشيفرة 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: `drop(c)` For more information about this error, try `rustc --explain E0040`. error: could not compile `drop-example` due to previous error
تبيّن لنا رسالة الخطأ هذه أنه من غير المسموح لنا استدعاء drop
صراحةً. تستخدم رسالة الخطأ المصطلح مُفكّك destructor وهو مصطلح برمجي عام لدالة تنظف نسخة ما؛ والمفكّك هو مصطلح معاكس للباني constructor وهو الذي ينشئ نسخةً ما، ودالة drop
في رست هي نوع من أنواع المفكّكات.
لا تسمح لنا رست باستدعاء drop
صراحةً لأن رست ستستدعي تلقائيًا التابع drop
على القيمة في نهاية الدالة main
مما سيسبب خطأ التحرير المزدوج double free لأن رست سيحاول تحرير القيمة ذاتها مرتين.
لا يمكننا تعطيل إدخال drop
التلقائي عندما تخرج قيمة ما عن النطاق، ولا يمكننا استدعاء التابع drop
صراحةً، لذا نحن بحاجة لإجبار القيمة على أن تُنظف مبكرًا باستخدام الدالة std::mem::drop
.
تعمل دالة std::mem::drop
بصورةٍ مختلفة عن التابع drop
في سمة Drop
، إذ نستدعيها بتمرير القيمة التي نريد تحريرها قسريًا مثل وسيط argument. الدالة مضمّنة في البداية لذا يمكننا تعديل الدالة main
في الشيفرة 15 بحيث تستدعي الدالة drop
كما في الشيفرة 16:
اسم الملف: 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."); }
[الشيفرة 16: استدعاء std::mem::drop
لتحرير القيمة صراحةً قبل الخروج من النطاق]
ينتج الخرج الآتي عن تنفيذ الشيفرة السابقة:
$ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished dev [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
بطرق عديدة وذلك بهدف جعل عملية تحرير الذاكرة سهلة ومريحة، إذ يمكنك مثلًا استخدامها لإنشاء مُخَصص ذاكرة memory allocator خاص بك. ولا داعي لتذكر عملية تحرير الذاكرة مع سمة Drop
وفي نظام رست للملكية وذلك لأن رست تفعل ذلك تلقائيًا، ولا داعي أيضًا للقلق من المشاكل الناتجة في حال تحرير قيم لا تزال قيد الاستخدام، إذ يضمن نظام الملكية أن المراجع صحيحة وأن drop
يُستدعى فقط عندما تكون القيمة غير مستخدمة بعد الآن.
الآن بعد أن رأينا Box<T>
وبعض من خصائص المؤشرات الذكية، لنرى بعض المؤشرات الذكية الأخرى المُعرفة في المكتبة القياسية.
ترجمة -وبتصرف- لقسم من الفصل Smart Pointers من كتاب The Rust Programming Language.
اقرأ أيضًا
- المقال التالي: المؤشر Rc<T> الذكي واستخدامه للإشارة إلى عدد المراجع في لغة رست Rust
- المقال السابق: معاملة المؤشرات الذكية Smart Pointers مثل مراجع نمطية Regular References باستخدام سمة Deref في لغة رست
- كتابة برنامج سطر أوامر Command Line بلغة رست Rust
- المراجع References والاستعارة Borrowing والشرائح Slices في لغة رست
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.