مقدمة: الفخ الذي يقع فيه جميع المبتدئين في Rust
\n\nأنت تتعلم Rust. لقد فهمت بالفعل مفهوم الملكية والاقتراض. أنت تكتب أول دالة جادة لك وتفكر: "حسنًا، لكي لا أنقل السلسلة النصية، سأمرر مرجعًا!". وهكذا تكتب:
\n\nfn print_string(s: &String) {\n println!("{}", s);\n}\n\nالمترجم سعيد. الكود يعمل. أنت سعيد. لكن المشكلة: مطور Rust متمرس، عندما يرى كودك، سيتجهم. لماذا؟ أليس كل شيء يعمل؟
\n\nهذا السؤال هو واحد من أكثر الأسئلة شيوعًا على StackOverflow. آلاف المطورين يتعثرون في هذه التفاصيل التي تبدو بسيطة. اليوم سنفهم لماذا &String و &Vec و &Box في توقيع الدالة تعتبر علامة حمراء، وكيفية الكتابة بشكل صحيح.
المشكلة رقم 1: تقييد الأنواع الضمني (Type Coercion)
\n\nRust لديها نظام قوي للتحويل التلقائي للأنواع، ولكن فقط في اتجاه واحد. هذا يُسمى Deref Coercion (التحويل عبر إلغاء المرجع).
\n\nالفكرة بسيطة: إذا كان لديك مرجع إلى String، يمكن لـ Rust تلقائيًا تحويله إلى مرجع إلى &str. نفس الشيء مع &Vec<T> → &[T] و &Box<T> → &T.
انظر إلى الفرق:
\n\n// سيء: يقبل فقط &String\nfn bad(s: &String) {\n println!("{}", s);\n}\n\n// جيد: يقبل &str\nfn good(s: &str) {\n println!("{}", s);\n}\n\nfn main() {\n let owned = String::from("مرحبًا بالعالم!");\n let slice = "مرحبًا، شريحة!";\n \n bad(&owned); // يعمل\n // bad(slice); // خطأ! slice هو &str، وليس &String\n \n good(&owned); // يعمل (تحويل تلقائي)\n good(slice); // يعمل (هذا بالفعل &str)\n}\n\nأرأيت؟ الدالة good تقبل أي سلسلة نصية: سواء كانت مملوكة String أو حرفية &str أو حتى جزءًا من سلسلة. الدالة bad تقبل فقط String. هذا تقييد غير ضروري.
مثال عملي: البحث عن نص فرعي
\n\n// بحث محدود\nfn find_in_string(haystack: &String, needle: &str) -> bool {\n haystack.contains(needle)\n}\n\n// بحث شامل\nfn find_in_str(haystack: &str, needle: &str) -> bool {\n haystack.contains(needle)\n}\n\nfn main() {\n let my_string = String::from("Rust رائع");\n let my_str = "Rust رائع";\n \n // يعمل فقط مع String\n find_in_string(&my_string, "Rust");\n // find_in_str(my_str, "Rust"); // خطأ في الترجمة!\n \n // يعمل مع كليهما\n find_in_str(&my_string, "Rust");\n find_in_str(my_str, "Rust");\n}\n\nالمشكلة رقم 2: الأداء والمراجع غير المباشرة الزائدة
\n\nدعنا ننظر "تحت الغطاء". ما هو String في الذاكرة؟ هو هيكل من ثلاثة حقول:
- \n
- مؤشر إلى البيانات في الكومة (heap) \n
- طول السلسلة النصية \n
- السعة (capacity) \n
عندما تمرر &String، فأنت تمرر مرجعًا إلى هذا الهيكل. للوصول إلى البيانات، يحتاج المترجم إلى:
- \n
- إلغاء مرجع
&String→ الحصول على هيكلString\n - أخذ المؤشر إلى البيانات في الكومة من الهيكل \n
- إلغاء مرجع هذا المؤشر → الحصول على بايتات السلسلة \n
عندما تمرر &str، فأنت تمرر مؤشرًا سمينًا (fat pointer) يتكون من حقلين فقط:
- \n
- مؤشر إلى البيانات \n
- الطول \n
يحتاج المترجم إلى خطوة واحدة فقط — إلغاء مرجع المؤشر. مراجع غير مباشرة أقل يعني كود أسرع. الفرق ضئيل جدًا، لكن في الحلقات الساخنة أو عند التمرير إلى