لماذا مراجعة الكود في Rust ليست مجرد فحص للأقواس
\n\nRust هي لغة تعد بالعمل الآمن مع الذاكرة والأداء العالي. ولكن حتى أكثر المترجمات صرامة لا تحمي من الأخطاء المنطقية، أو الكود غير الاصطلاحي، أو الاستخدام غير المبرر لـ unsafe. مراجعة الكود في Rust هي فن. أنت لا تنظر فقط إلى ما إذا كانت الأقواس المعقوفة مغلقة. أنت تتحقق من كيفية تجاوز المطور (أو عدم تجاوزه) لقيود borrow checker، ومدى كفاءة استخدامه للمكررات، وما إذا كان يحاول "خداع" النظام باستخدام المؤشرات الخام.
في هذه المقالة، سأشرح الجوانب التي يجب الانتباه إليها عند مراجعة الكود في Rust. بدون نظرية زائدة - فقط نصائح عملية وأمثلة وقائمة مراجعة يمكنك تعليقها على الحائط (أو حفظها في الإشارات المرجعية). هيا بنا!
\n\n1. الملكية، الاقتراض وأوقات الحياة: المصدر الرئيسي للألم
\n\nأول ما يجب التحقق منه هو ما إذا كان نظام الملكية يعمل بشكل صحيح. حتى إذا تم تجميع الكود، فهذا لا يعني أنه صحيح. المشاكل النموذجية:
\n\n- \n
- الاستنساخ المفرط. عندما كان من الممكن استخدام مرجع، يقوم المطور بنسخ البيانات. هذا يقتل الأداء. \n
- الاقتراض القابل للتغيير وغير القابل للتغيير. إذا كان هناك
&mut Tو&Tفي نفس النطاق في نفس الوقت - سيكون هناك خطأ. لكن في بعض الأحيان يمكن إعادة ترتيب الكود لتجنب ذلك. \n - أوقات الحياة غير الصحيحة (lifetimes). خاصة في الهياكل المعقدة التي تحتوي على مراجع متعددة. \n
مثال: كود سيء
\nfn process(data: &Vec<i32>) -> i32 {\n let mut sum = 0;\n for &x in data.iter() {\n sum += x;\n }\n // كل شيء على ما يرام، لكن لماذا تمرر &Vec إذا كان بإمكانك &[i32]؟\n sum\n}\n\nكود جيد
\nfn process(data: &[i32]) -> i32 {\n data.iter().sum()\n}\n\nما يجب التحقق منه في المراجعة: هل يتم تمرير &Vec أو &String أو &HashMap حيث يكفي slice؟ هل يتم استخدام clone() بدون ضرورة؟ هل تم وضع تعليقات أوقات الحياة بشكل صحيح؟
2. Unsafe: المنطقة الحمراء
\n\nكتل unsafe هي مثل فتحة مفتوحة في Rust الآمن. يجب أن تكون مبررة، وضئيلة، وموثقة بعناية. في مراجعة الكود، انتبه إلى:
- \n
- لماذا يتم استخدام unsafe؟ غالبًا ما يمكن الاستغناء عن التجريدات الآمنة. \n
- صحة المؤشرات الخام. هل يتم إلغاء مرجعية مؤشر فارغ؟ هل هناك مرجع معلق بعد تحرير الذاكرة؟ \n
- الثوابت (invariants). يجب أن يصف الكود الذي يحتوي على
unsafeبوضوح الثوابت التي يحافظ عليها. \n
مثال: unsafe خطير
\nunsafe fn dangerous() {\n let ptr = 0x12345 as *mut i32;\n *ptr = 42; // ماذا لو لم يكن هناك شيء في هذا العنوان؟\n}\n\nهذا الكود يجب ألا يجتاز المراجعة. حتى لو "كان يعمل" - فهذه ليست حجة.
\n\nUnsafe جيد
\nunsafe fn init_buffer(buf: &mut [u8]) {\n // نحن متأكدون من أن buf يشير إلى ذاكرة صالحة،\n // لأن الطرف المستدعي يضمن ذلك.\n std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len());\n}\n\nما يجب التحقق منه: هل يوجد تعليق يشرح سبب كل unsafe؟ هل هناك اختبارات تتحقق من الحالات الحدودية؟ هل يتم انتهاك ضمانات الأمان؟
3. الإغلاقات والأسلوب الوظيفي
\n\nتشجع Rust الأسلوب الوظيفي: المكررات، الإغلاقات، map، filter، fold. لكن هذا قد يؤدي إلى التعقيد المفرط. في المراجعة، انظر إلى:
- \n
- هل يتم الإفراط في استخدام الإغلاقات؟ إذا كان الإغلاق يلتقط العديد من المتغيرات، فربما يجب استخراج المنطق في دالة منفصلة.