مقدمة: لغز "mut" المزدوج
\nإذا كنت قد كتبت بلغة Rust من قبل، فبالتأكيد رأيت الازدواجية الغريبة للكلمة المفتاحية mut. يمكن أن تأتي قبل اسم المتغير (let mut x = 5)، أو بعد النقطتين، بجانب النوع (&mut T). للوهلة الأولى - شيء بسيط، لكن هذا الموضوع حصد آلاف المشاهدات على Stack Overflow. لماذا؟ لأنه خلف هذا البساطة يكمن فرق جوهري بين الملكية والمرجع.
في هذه المقالة، لن نجيب فقط على سؤال "أين نضع mut؟". سننظر تحت غطاء Rust، ونحلل كيف يعالج المترجم قابلية التغيير، ولماذا هذا الفرق هو مفتاح العمل الآمن مع الذاكرة. إذا كنت تتعلم Rust أو تريد فهمًا أعمق لنموذج الملكية الخاص بها - فأنت في المكان الصحيح.
\n\nالجزء الرئيسي: عالمين - mut واحد
\n1. mut الأول: "أنا المالك ويمكنني التغيير"
\nعندما تكتب:
\nlet mut x = 42;\nx = 10; // صحيح\n\nفأنت تقول للمترجم: "أنا أنشئ متغيرًا x يملك القيمة 42، وأسمح بتغيير هذه القيمة لاحقًا". بدون mut، يكون المتغير غير قابل للتغيير (ثابت) افتراضيًا. هذا اختيار تصميمي واعٍ في Rust: الافتراضي هو الأمان، والتغيير هو إذن صريح.
تحت الغطاء: يخصص المترجم مساحة على المكدس (أو الكومة إذا كان Box) ويضع علامة على خلية الذاكرة على أنها "قابلة للتغيير". ستراقب مدققات الاقتراض (borrow checker) لضمان عدم وجود مراجع قابلة للتغيير وغير قابلة للتغيير لهذه الذاكرة في نفس الوقت. إذا لم يكن mut موجودًا، فإن أي محاولة لتعيين قيمة جديدة ستؤدي إلى خطأ في الترجمة.
2. mut الثاني: "أعطيك المفاتيح، لكنني لا أتخلى عن المنزل"
\nالآن مثال مع مرجع:
\nlet mut x = 42;\nlet y = &mut x; // مرجع قابل للتغيير إلى x\n*y = 100;\n\nهنا &mut T لا يتعلق بالملكية، بل بـ الاقتراض. نقول: "أنا أعير x للمتغير y، مع إعطاء الحق في تغييره". يظل x نفسه هو المالك، لكنه ينقل السيطرة مؤقتًا.
الفرق الرئيسي: mut بعد النقطتين يتعلق بـ نوع المرجع، وليس بالمتغير. لا يمكنك كتابة let &mut x = &y - فهذا غير صحيح نحويًا. يميز Rust بدقة بين:
- \n
let mut x- متغير مالك قابل للتغيير \n&mut T- مرجع قابل للتغيير إلى قيمة من النوع T \n
هذا هو بالضبط ما يربك المبتدئين: بصريًا، "mut" في أماكن مختلفة، ودلاليًا - يحل مهامًا مختلفة.
\n\n3. لماذا هذا مهم: مدقق الاقتراض (borrow checker)
\nتخيل أنك تكتب دالة تأخذ مرجعًا:
\nfn increment(n: &mut i32) {\n *n += 1;\n}\n\nfn main() {\n let mut a = 5;\n increment(&mut a);\n println!("{}", a); // 6\n}\n\nإذا كتبنا fn increment(n: &i32) (بدون mut)، فلن نتمكن من تغيير n. وإذا نسينا mut في a - فلن نتمكن من تمرير مرجع قابل للتغيير. يحميك مترجم Rust من التغيير العرضي للبيانات الذي قد يؤدي إلى سباق البيانات أو سلوك غير محدد.
تحت الغطاء: &mut T هو مرجع فريد (حصري). في أي لحظة زمنية، يمكن أن يوجد مرجع واحد فقط قابل للتغيير على البيانات. تضمن هذه القاعدة عدم قراءة أي شخص آخر للبيانات أثناء تغييرها. يمكن أن يكون هناك أي عدد من المراجع غير القابلة للتغيير - فهي تقرأ فقط.
4. مثال عملي حقيقي: الهياكل والطرق
\nغالبًا ما يحدث الارتباك عند العمل مع الطرق:
\nstruct Point {\n x: i32,\n y: i32,\n}\n\nimpl Point {\n fn shift(&mut self, dx: i32, dy: i32) {\n self.x += dx;\n self.y += dy;\n }\n}\n\nfn main() {\n let mu