مقدمة
Rust هي لغة برمجة أنظمة قوية تكتسب شعبية بفضل أمانها وأدائها. ومع ذلك، فإن نظام الملكية (ownership) والاقتراض (borrowing) الخاص بها غالبًا ما يربك المبتدئين. في هذه المقالة، سنشرح أهم أخطاء مطوري Rust المبتدئين، حتى تتمكن من تجنبها والتعود على اللغة بشكل أسرع. سنناقش المشكلات النموذجية المتعلقة بالاقتراض، وأوقات الحياة (lifetimes)، والسلاسل النصية، والعمل مع الذاكرة.
1. تجاهل قواعد الاقتراض (Borrow Checker)
الخطأ الأكثر شيوعًا هو محاولة الحصول على مرجع قابل للتغيير وآخر غير قابل للتغيير لنفس البيانات في نفس الوقت. تمنع Rust ذلك بشكل صارم في مرحلة التجميع.
المشكلة: المراجع المتعددة
fn main() { let mut data = vec![1, 2, 3]; let ref1 = &data; // مرجع غير قابل للتغيير let ref2 = &mut data; // مرجع قابل للتغيير — خطأ! println!("{} {}", ref1[0], ref2[0]);}سيصدر المترجم خطأ: cannot borrow `data` as mutable because it is also borrowed as immutable.
الحل: نطاق الرؤية الصحيح
fn main() { let mut data = vec![1, 2, 3]; { let ref1 = &data; // مرجع غير قابل للتغيير println!("{}", ref1[0]); } // ref1 يخرج من النطاق let ref2 = &mut data; // الآن يمكن ref2[0] = 10; println!("{}", ref2[0]);}راقب دائمًا وقت حياة المراجع. إذا كنت بحاجة للعمل مع مرجع قابل للتغيير وآخر غير قابل للتغيير، فافصل بينهما في كتل مختلفة.
2. العمل غير الصحيح مع السلاسل النصية: String مقابل &str
غالبًا ما يخلط المبتدئون بين String (نوع مالك، قابل للتغيير) و &str (مرجع إلى شريحة نصية). يؤدي هذا إلى أخطاء عند تمرير السلاسل النصية إلى الدوال.
المشكلة: توقع الملكية بدلاً من المرجع
fn print_length(s: String) { println!("الطول: {}", s.len());}
fn main() { let name = "أليس".to_string(); print_length(name); println!("الاسم: {}", name); // خطأ! name تم نقله}الحل: استخدم &str للمعاملات
fn print_length(s: &str) { println!("الطول: {}", s.len());}
fn main() { let name = "أليس".to_string(); print_length(&name); // نمرر مرجعًا println!("الاسم: {}", name); // الآن يعمل}القاعدة: إذا كانت الدالة لا يجب أن تستحوذ على الملكية، استخدم &str. هذا يجعل الكود مرنًا — يمكنك تمرير كل من &String والنصوص الحرفية "نص".
3. أخطاء وقت الحياة (Lifetimes)
عندما تعيد دالة مرجعًا، تطلب Rust تحديد المدة التي سيعيشها هذا المرجع. غالبًا ما ينسى المبتدئون التعليقات التوضيحية لـ lifetimes.
المشكلة: lifetimes الضمنية
fn longest(x: &str, y: &str) -> &str { // خطأ: missing lifetime specifier if x.len() > y.len() { x } else { y }}الحل: أضف lifetime مشتركًا
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}
fn main() { let s1 = String::from("قصير"); let s2 = "سلسلة طويلة"; let result = longest(&s1, &s2); println!("الأطول: {}", result);}التعليق التوضيحي 'a يخبر المترجم أن المرجع المُعاد سيعيش لمدة لا تتجاوز أقصر معاملات الإدخال. هذا يضمن أمان الذاكرة.
4. تجاهل Option و Result
Rust لا تحتوي على null. بدلاً من ذلك، يتم استخدام Option<T> لاحتمال غياب القيمة و Result<T, E> لمعالجة الأخطاء. غالبًا ما يحاول المبتدئون إلغاء مرجعية Option دون التحقق.
المشكلة: الذعر عند استخدام unwrap
fn find_user(id: u32) -> Option<String> { if id == 1 { Some("آنا".to_string()) }