اذهب إلى المحتوى

تنفيذ شيفرة برمجية عند تحرير الذاكرة cleanup باستخدام السمة Drop في لغة رست


Naser Dakhel

السمة الثانية المهمة لنمط المؤشرات الذكية 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.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...