لماذا لا تسمح لك Rust بتخزين مرجع إلى نفسها: تحليل بالأمثلة
\n\nتخيل موقفًا: أنت تكتب برنامجًا بلغة Rust، وتحتاج إلى إنشاء هيكل يخزن بعض البيانات، بالإضافة إلى مرجع إلى نفس هذه البيانات. منطقي؟ بالتأكيد! لكن مترجم Rust فجأة يظهر خطأ، وأنت في حيرة. هل هذا مألوف؟ هذا السؤال هو من أكثر الأسئلة شيوعًا على Stack Overflow (آلاف المشاهدات)، واليوم سنقوم بتحليله بالكامل.
\n\nفي هذا الدليل التعليمي خطوة بخطوة، لن نفهم فقط لماذا لا يمكن تخزين قيمة ومرجع في نفس الهيكل في Rust، بل سنتعلم أيضًا كيفية تجاوز هذا القيد بطرق آمنة. هيا بنا!
\n\nالمشكلة: محاولة ساذجة
\n\nلنفترض أننا نريد إنشاء هيكل يخزن سلسلة نصية وحرفها الأول (مرجع إلى الحرف الأول). إليك كيف يبدو ذلك في الكود:
\n\nstruct MyStruct {\n data: String,\n first_char: &str, // مرجع إلى data\n}\n\nfn main() {\n let s = String::from("Hello");\n let first = &s[0..1]; // مرجع إلى الحرف الأول\n \n let my_struct = MyStruct {\n data: s,\n first_char: first, // نحاول حفظ المرجع\n };\n}\n\n\nيظهر المترجم خطأ يحير العديد من المبتدئين. دعنا نفهم ما حدث بالضبط.
\n\nلماذا تمنع Rust الهياكل المرجعية الذاتية؟
\n\nجذر المشكلة يكمن في نظام الملكية (ownership) والاقتراض (borrowing) في Rust. عندما تنقل (move) القيمة s إلى الحقل data، فإنها تُنسخ فعليًا إلى موقع جديد في الذاكرة. المرجع first في هذه الأثناء لا يزال يشير إلى العنوان القديم، الذي أصبح الآن غير صالح.
في Rust، يجب أن يكون كل المرجع صالحًا طوال فترة وجوده. الهياكل المرجعية الذاتية في Rust تخالف هذه القاعدة، لأن المرجع والقيمة يعيشان في نفس الكائن، وعند نقل الهيكل، يصبح المرجع معلقًا (dangling reference).
\n\nماذا يحدث تحت الغطاء؟
\n\nعندما تكتب هذا الكود:
\n\nlet s = String::from("Hello");\nlet first = &s[0..1];\nlet my_struct = MyStruct { data: s, first_char: first };\n\n\nيحدث التالي:
\n- \n
sيملك السلسلة "Hello" في الكومة. \n firstهو مرجع لجزء من هذه السلسلة. \n - عند إنشاء الهيكل
data: s، تنتقل ملكية السلسلة إلى الهيكل. قد تنتقل السلسلة في الذاكرة. \n - المرجع
firstلا يزال يشير إلى العنوان القديم - هذا مرجع معلق. لا يمكن لـ Rust السماح بذلك. \n
Borrow checker في Rust يرى هذا الخطر المحتمل ويمنع الكود في مرحلة التجميع. هذا ليس خطأ، بل ميزة تحميك من العديد من المشاكل في وقت التشغيل.
\n\nسيناريوهات حقيقية تحتاج فيها إلى هياكل مرجعية ذاتية
\n\nعلى الرغم من المنع، قد تكون هذه الهياكل مفيدة. إليك حالات نموذجية:
\n\n- \n
- المحللات (Parsers) وشجرة النحو المجرد (AST): عندما تشير عقد الشجرة إلى بعضها البعض. \n
- محركات الألعاب: كيانات تحتوي على مراجع لمكوناتها. \n
- بروتوكولات الشبكات: مخازن مؤقتة بمؤشرات داخلية. \n
- أنظمة الإضافات: عندما يحتوي كائن على مرجع إلى إعداداته. \n
الخبر الجيد: هناك طرق آمنة لتجاوز هذا القيد!
\n\nالحل 1: استخدام الفهارس بدلاً من المراجع (أبسط طريقة)
\n\nأبسط وأكثر طريقة آمنة هي تخزين فهرس أو مفتاح بدلاً من المرجع. بدلاً من &str، قم بتخزين usize (موضع الحرف الأول).
struct MyStruct {\n data: String,\n first_char_index: usize, // نخزن الموضع، وليس المرجع\n}\n\nimpl MyStruct {\n fn first_char(&self) -> &