لماذا لا تحتاج Rust إلى جامع قمامة ولكنها آمنة؟
\n\nعندما يسمع المطورون المعتادون على Python أو Java أو C# عن Rust لأول مرة، غالبًا ما يخيفهم غياب جامع القمامة (GC) المعتاد. «كيف ذلك؟ — يفكرون. — بدون GC سنعود إلى عصر الإدارة اليدوية للذاكرة، والتسريبات، وأخطاء التجزئة؟»
\n\nالمفاجأة: لا، لن نعود. تقدم Rust طريقًا ثالثًا — نظام الملكية (ownership) الذي يضمن أمان الذاكرة في مرحلة التجميع. لا وقت تشغيل، ولا GC، ولا مؤشرات معلقة. فقط قواعد واضحة ومترجم لن يسمح لك بإطلاق النار على قدمك.
\n\nفي هذه المقالة، سنشرح كيف تعمل الذاكرة في Rust، وما هو RAII، والملكية، والاقتراض، ومدة الحياة، ولماذا كل هذا يجعل Rust واحدة من أكثر لغات البرمجة النظامية موثوقية.
\n\nكيف تعمل الذاكرة في Rust: المكدس والكومة
\n\nكما في أي لغة منخفضة المستوى، تنقسم الذاكرة في Rust إلى منطقتين رئيسيتين:
\n\n- \n
- المكدس (stack) — ذاكرة سريعة ومحدودة الحجم تعمل وفق مبدأ LIFO. هنا تُخزَّن المتغيرات المحلية ووسائط الدوال وعناوين العودة. \n
- الكومة (heap) — ذاكرة أبطأ ولكنها مرنة، تُستخدم للبيانات التي لا يُعرف حجمها في مرحلة التجميع (مثل
StringوVecوBox). \n
في C++، أنت من يقرر متى تخصص وتحرر الكومة (عبر new/delete). في Java أو Python، يقوم جامع القمامة بذلك نيابة عنك، ويوقف البرنامج بشكل دوري. أما Rust فتستخدم نظام الملكية الذي يحرر الذاكرة تلقائيًا بمجرد خروج المتغير من نطاق الرؤية.
مثال: تخصيص الذاكرة الأساسي
\n\nfn main() {\n let s = String::from("مرحبًا بالعالم!"); // تخصيص ذاكرة في الكومة\n println!("{}", s);\n} // هنا يخرج s من نطاق الرؤية، وتُحرر الذاكرة\n\nلاحظ: لا free() ولا delete. في نهاية الكتلة، يُدرج المترجم ضمنيًا استدعاءً للمدمر — drop(). يُسمى هذا RAII (اكتساب الموارد عند التهيئة) — يُلتقط المورد عند التهيئة ويُحرر عند تدمير الكائن.
الملكية (Ownership): القاعدة الرئيسية في Rust
\n\nنظام الملكية في Rust مبني على ثلاث قواعد بسيطة:
\n\n- \n
- لكل قيمة في Rust مالك واحد (owner) بالضبط. \n
- عندما يخرج المالك من نطاق الرؤية، تُحذف القيمة. \n
- يمكن نقل المالك (move) أو إعارته مؤقتًا (borrow). \n
يبدو مملًا؟ دعنا نرى كيف يعمل هذا عمليًا.
\n\nالنقل (Move)
\n\nfn main() {\n let s1 = String::from("hello");\n let s2 = s1; // يُنقل s1 إلى s2\n // println!("{}", s1); // خطأ! s1 لم يعد يملك البيانات\n println!("{}", s2); // صحيح\n}\n\nفي C++، كان هذا الإسناد سيؤدي إلى مؤشرين يشيران إلى نفس الذاكرة — وإلى تحرير مزدوج. في Rust، يمنعك المترجم ببساطة من استخدام s1 بعد النقل. لا تسريبات، ولا حذف مزدوج.
النسخ (Copy)
\n\nالأنواع البسيطة، مثل الأعداد الصحيحة والقيم المنطقية والأحرف، تطبق الخاصية Copy. بالنسبة لها، يُنشئ الإسناد نسخة كاملة:
fn main() {\n let x = 42;\n let y = x;\n println!("x = {}, y = {}", x, y); // كلاهما متاح!\n}\n\nلماذا؟ لأن i32 يُخزن على المكدس، ونسخه هو مجرد نسخ بضع بايتات. رخيص وآمن.
الاقتراض (Borrowing) والمراجع
\n\nماذا تفعل إذا كنت بحاجة إلى إعطاء وصول مؤقت إلى البيانات دون نقل الملكية؟ استخدم المراجع. تتيح لك Rust اقتراض القيمة عبر مرجع.
\n\nfn main() {\n let s = String::from(