مقدمة: لماذا هذه مشكلة أصلاً؟
\nإذا كنت قد كتبت أي شيء في Rust أكثر تعقيداً من "Hello, World!"، فمن المحتمل أنك واجهت موقفاً حيث يصرخ المترجم حرفياً: "expected &'static str, but found String". هل هذا مألوف؟ هذا أحد أكثر الأسئلة شيوعاً على Stack Overflow (عشرات الآلاف من المشاهدات)، وليس من دون سبب.
\nالأمر أن String و &'static str هما نوعان مختلفان تماماً. String هو نوع مالك يمكن أن ينمو ويتغير أثناء التنفيذ. أما &'static str فهو مرجع لشريحة سلسلة تعيش طوال البرنامج. هذا يشبه مقارنة حقيبة بها أغراض (String) برف مثبت بإحكام (&'static str).
في هذا الدليل، سنستعرض جميع الطرق القانونية لتحويل String إلى &'static str، من البسيطة إلى المتقدمة. نعم، سأريكم كيف لا تؤذوا أنفسكم في هذه العملية.
ما هو &'static str في الواقع؟
\nقبل أن نبدأ بكتابة الكود، دعنا نفهم النظرية. &'static str هو مرجع لسلسلة مضمون أنها تعيش طوال مدة تشغيل البرنامج. عادةً ما تكون هذه السلاسل مضمنة بشكل ثابت في الملف الثنائي:
let hello: &'static str = "Hello, world!"; // يعيش للأبد\n\nولكن ماذا لو تم إنشاء السلسلة ديناميكياً؟ على سبيل المثال، تُقرأ من ملف أو تأتي من الشبكة؟ هنا يبدأ الجزء المثير للاهتمام.
\n\nالطريقة رقم 1: الأسهل — تسرب الذاكرة (نعم، هذا قانوني)
\nنعم، لم تخطئ السمع. في Rust يوجد تسرب ذاكرة متعمد من خلال الدالة Box::leak. هذا ليس خطأ، بل ميزة! أنت تقول حرفياً للمترجم: "انسَ هذه الذاكرة، إنها ملكي إلى الأبد".
use std::boxed::Box;\n\nfn string_to_static_str(s: String) -> &'static str {\n // تحويل String إلى Box<str>، ثم "نسيانها"\n let boxed_str: Box<str> = s.into_boxed_str();\n // الآن هذه السلسلة ستعيش إلى الأبد\n Box::leak(boxed_str)\n}\n\nfn main() {\n let dynamic_string = String::from("لقد وُلدت ديناميكياً!");\n let static_ref: &'static str = string_to_static_str(dynamic_string);\n println!("{}", static_ref);\n // لن يتم تحرير الذاكرة أبداً\n}\n\nالإيجابيات: بسيط، فعال، لا يتطلب كريتات خارجية.
\nالسلبيات: تفقد السيطرة على الذاكرة إلى الأبد. إذا استدعيت هذا في حلقة — مرحباً، OOM (نفاد الذاكرة).
متى يكون هذا مناسباً؟
\n- \n
- عندما تعرف بالتأكيد أن السلسلة مطلوبة طوال عمر البرنامج (مثل الإعدادات التي تُحمّل مرة واحدة عند بدء التشغيل). \n
- في الأنظمة المضمنة حيث لا يوجد كومة وكل شيء ثابت. \n
الطريقة رقم 2: Lazy Static — للكسالى والأذكياء
\nكريت lazy_static هو كلاسيكي في Rust. يسمح لك بإنشاء متغير ثابت يتم تهيئته فقط عند أول استخدام. مثالي للإعدادات والبيانات العامة.
\n// أضف إلى Cargo.toml: lazy_static = "1.4"\nuse lazy_static::lazy_static;\n\nlazy_static! {\n static ref MY_STRING: String = {\n // لنفترض أننا نقرأ سلسلة من ملف\n String::from("تم التحميل بتكاسل!")\n };\n}\n\nfn get_static_str() -> &'static str {\n // lazy_static يعطي تلقائياً &'static str\n &MY_STRING\n}\n\nfn main() {\n println!("{}", get_static_str());\n}\n\nالإيجابيات: \n
- \n
- آمن (لا تسرب للذاكرة). \n
- تهيئة كسولة — يتم إنشاء السلسلة فقط عند الحاجة. \n
- يعمل مع أي أنواع، وليس فقط السلاسل. \n
- \n
- اعتماد على كريت خارجي. \n
- قد يكون مبالغاً فيه للحالات البسيطة. \n
الطريقة رقم 3: OnceCell و OnceLock — البديل الحديث
\nبدءاً من Rust 1.70، في المعيار