كيفية تحويل Option<String> إلى Option<&str> في Rust بدون ألم

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا أنت هنا؟ اعترف بصراحة

\n\n

أنت تكتب بلغة Rust، وفجأة تصطدم بحائط. لديك Option<String> — قيمة قد تكون موجودة وقد لا تكون. والدالة التي تريد استدعاءها تتطلب Option<&str>. والمترجم ينظر إليك بتوبيخ، مصدرًا خطأ يجعلك ترغب في إغلاق الكمبيوتر والذهاب لشرب الشاي.

\n\n

لا تقلق. هذا الموقف شائع جدًا لدرجة أن سؤال "تحويل Option<String> إلى Option<&str>" على Stack Overflow حصل على آلاف المشاهدات. أنت لست وحدك.

\n\n

في هذه المقالة، سنفهم لماذا يسبب هذا التحويل الكثير من الأسئلة، والأهم من ذلك — كيفية القيام به بشكل صحيح. بدون سحر، بدون نسخ غير ضروري للذاكرة، مع فهم كامل لما يحدث تحت الغطاء.

\n\n

المشكلة: لماذا لا يمكنك التحويل ببساطة؟

\n\n

أولاً — نظرة سريعة على الأنواع.

\n\n
    \n
  • String — هو نوع مالك. هو يملك البيانات في الكومة (heap) وهو المسؤول عن تحريرها.
  • \n
  • &str — هو مرجع إلى شريحة نصية. هو يقترض البيانات، دون امتلاكها.
  • \n
\n\n

وهنا تبدأ المتعة. Option<String> هي ربما سلسلة نصية تمتلكها. Option<&str> هي ربما مرجع إلى سلسلة نصية.

\n\n

المشكلة هي أنه لا يمكنك ببساطة أخذ مرجع إلى القيمة داخل Option وإعطائها للخارج، إذا كان Option مؤقتًا. يجب أن يشير المرجع إلى شيء ما، وهذا "الشيء" يجب أن يعيش لفترة كافية.

\n\n

دعنا نلقي نظرة على خطأ المبتدئ النموذجي:

\n\n
fn 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).

\n\n

الحل رقم 1: as_deref() — صديقك الجديد الأفضل

\n\n

الطريقة الأكثر أناقة لتحويل Option<String> إلى Option<&str> هي استخدام الدالة as_deref().

\n\n

كيف يعمل هذا؟ as_deref() يقوم بأمرين:

\n
    \n
  1. يأخذ &Option<T> ويحوله إلى Option<&T> (هذا ما يفعله as_ref()).
  2. \n
  3. ثم يطبق Deref على القيمة الداخلية، محولاً &String إلى &str.
  4. \n
\n\n

إليك كيف يبدو هذا في الكود:

\n\n
fn 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 حي.

\n\n

ماذا لو كان Option فارغًا؟

\n

الأمر بسيط:

\n\n
fn main() {\n    let empty: Option<String> = None;\n    let result: Option<&str> = empty.as_deref();\n    println!("{:?}", result); // None\n}
\n\n

لا ذعر، ولا أعطال. يعمل كالساعة.

المدونات

توصيات الكتب