لماذا أنت هنا؟ اعترف بصراحة
\n\nأنت تكتب بلغة Rust، وفجأة تصطدم بحائط. لديك Option<String> — قيمة قد تكون موجودة وقد لا تكون. والدالة التي تريد استدعاءها تتطلب Option<&str>. والمترجم ينظر إليك بتوبيخ، مصدرًا خطأ يجعلك ترغب في إغلاق الكمبيوتر والذهاب لشرب الشاي.
لا تقلق. هذا الموقف شائع جدًا لدرجة أن سؤال "تحويل Option<String> إلى Option<&str>" على Stack Overflow حصل على آلاف المشاهدات. أنت لست وحدك.
\n\nفي هذه المقالة، سنفهم لماذا يسبب هذا التحويل الكثير من الأسئلة، والأهم من ذلك — كيفية القيام به بشكل صحيح. بدون سحر، بدون نسخ غير ضروري للذاكرة، مع فهم كامل لما يحدث تحت الغطاء.
\n\nالمشكلة: لماذا لا يمكنك التحويل ببساطة؟
\n\nأولاً — نظرة سريعة على الأنواع.
\n\n- \n
String— هو نوع مالك. هو يملك البيانات في الكومة (heap) وهو المسؤول عن تحريرها. \n &str— هو مرجع إلى شريحة نصية. هو يقترض البيانات، دون امتلاكها. \n
وهنا تبدأ المتعة. Option<String> هي ربما سلسلة نصية تمتلكها. Option<&str> هي ربما مرجع إلى سلسلة نصية.
المشكلة هي أنه لا يمكنك ببساطة أخذ مرجع إلى القيمة داخل Option وإعطائها للخارج، إذا كان Option مؤقتًا. يجب أن يشير المرجع إلى شيء ما، وهذا "الشيء" يجب أن يعيش لفترة كافية.
دعنا نلقي نظرة على خطأ المبتدئ النموذجي:
\n\nfn process(value: Option<String>) {\n // خطأ! cannot return reference to temporary value\n let ref_value: Option<&str> = value.as_ref().map(|s| s.as_str());\n // أو بشكل أكثر مباشرة:\n let ref_value: Option<&str> = Some(value.unwrap().as_str()); // BOOM!\n}\n\nالمترجم ينفجر باللون الأحمر. لماذا؟ لأن value هو مالك السلسلة النصية. عندما تحاول أخذ &str منها، يجب أن يعيش المرجع لمدة لا تزيد عن عمر value نفسه. وإذا كان value كائنًا مؤقتًا (مثل نتيجة استدعاء دالة)، يصبح المرجع معلقًا (dangling reference).
الحل رقم 1: as_deref() — صديقك الجديد الأفضل
\n\nالطريقة الأكثر أناقة لتحويل Option<String> إلى Option<&str> هي استخدام الدالة as_deref().
كيف يعمل هذا؟ as_deref() يقوم بأمرين:
- \n
- يأخذ
&Option<T>ويحوله إلىOption<&T>(هذا ما يفعلهas_ref()). \n - ثم يطبق
Derefعلى القيمة الداخلية، محولاً&Stringإلى&str. \n
إليك كيف يبدو هذا في الكود:
\n\nfn main() {\n let my_string: Option<String> = Some(String::from("مرحبًا، Rust!"));\n \n // سطر واحد من السحر:\n let my_str_ref: Option<&str> = my_string.as_deref();\n \n match my_str_ref {\n Some(s) => println!("السلسلة: {}", s),\n None => println!("لا توجد سلسلة"),\n }\n // my_string لا يزال حيًا وبصحة جيدة!\n println!("السلسلة الأصلية: {:?}", my_string);\n}\n\nلاحظ: my_string يظل مالكًا للبيانات. نحن فقط أخذنا مرجعًا لمحتواها. هذا يعني أن my_str_ref يعيش فقط طالما أن my_string حي.
ماذا لو كان Option فارغًا؟
\nالأمر بسيط:
\n\nfn main() {\n let empty: Option<String> = None;\n let result: Option<&str> = empty.as_deref();\n println!("{:?}", result); // None\n}\n\nلا ذعر، ولا أعطال. يعمل كالساعة.