من Option إلى Result في Rust: دليل تعليمي بدون ماكرو

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

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

ابدأ الدورة

لماذا مللت من كتابة الماكرو لتحويل Option إلى Result؟

أنت تكتب بلغة Rust، وفجأة تدرك: لديك Option<T>، ولكنك تحتاج Result<T, E>. قد يبدو الأمر بسيطًا. لكن إذا كنت مبتدئًا، فإن أول فكرة تخطر ببالك: "ربما أحتاج إلى ماكرو!" أو ربما كنت مطورًا متمرسًا، ولكنك في كل مرة تكتب نفس الشيء: match option { Some(v) => Ok(v), None => Err("error") }. اعترف بذلك، هذا مرهق.

السؤال على Stack Overflow "هل هناك طريقة لتبسيط تحويل Option إلى Result بدون ماكرو؟" حصل على آلاف المشاهدات. هذا ليس من قبيل الصدفة: المشكلة مألوفة لكل من يعمل مع Rust. لكن الخبر السار هو أن الحل موجود، وهو بسيط بشكل مدهش. في هذا الدليل التعليمي، سنستعرض جميع الطرق، من الأساسية إلى الأنيقة، بدون ماكرو واحد.

لن نقدم الإجابة فقط - بل سنوضح لك كيف تفكر كمطور Rust. هيا بنا!



ما هما Option و Result في Rust؟

قبل التحويل، دعنا نتأكد من أننا على نفس الموجة.

  • Option<T> هو نوع يمكن أن يكون إما Some(T) (توجد قيمة) أو None (لا توجد قيمة). يُستخدم عندما يكون غياب القيمة حالة طبيعية (مثل البحث عن عنصر في مجموعة).
  • Result<T, E> هو نوع يمكن أن يكون إما Ok(T) (كل شيء على ما يرام) أو Err(E) (حدث خطأ). يُستخدم للعمليات التي قد تفشل (مثل قراءة ملف).

المشكلة: أحيانًا يكون لديك Option، ولكن وفقًا لمنطق البرنامج، فإن غياب القيمة يعتبر خطأ. هنا تحتاج إلى التحويل.



الطريقة الأساسية: match (لكنك تعرفها بالفعل)

الطريقة الأكثر وضوحًا والتي يستخدمها المبتدئون غالبًا هي match. نعم، إنها تعمل، لكنها مطولة:

fn get_user_name(id: u32) -> Option<String> {    // ... بعض المنطق    if id == 1 { Some("Alice".to_string()) } else { None }}

fn main() { let user_id = 2; let name = match get_user_name(user_id) { Some(name) => Ok(name), None => Err("User not found"), }; println!("{:?}", name); // Err("User not found")}

هذا الكود صحيح، ولكن إذا كان لديك عشرات من هذه التحويلات في المشروع، فسوف تتعب من كتابة match. علاوة على ذلك، من السهل ارتكاب خطأ ونسيان معالجة None. نحتاج إلى شيء أكثر إيجازًا.



طريقة ok_or: صديقك الجديد المفضل

توفر المكتبة القياسية لـ Rust طريقة ok_or لـ Option. إنها تفعل بالضبط ما نحتاجه: تحول Some(v) إلى Ok(v)، و None إلى Err(error)، حيث error هي القيمة التي تمررها.

fn main() {    let user_id = 2;    let name = get_user_name(user_id).ok_or("User not found");    println!("{:?}", name); // Err("User not found")}

سطر واحد فقط! لكن هناك فارق بسيط: ok_or تأخذ قيمة بالملكية (owned value). إذا قمت بتمرير نص حرفي (string literal)، كما في المثال، فهذا يعمل لأن &str ينفذ Into<String>. ولكن إذا كنت بحاجة إلى تمرير خطأ مخزن في مكان آخر، فقد يؤدي ذلك إلى نسخ غير ضروري.

لمثل هذه الحالات، يوجد ok_or_else - يأخذ إغلاقًا (closure) يتم استدعاؤه فقط إذا كان Option يساوي None. هذا يوفر الموارد:

fn main() {    let user_id = 2;    let error_msg = format!("User with id {} not found", user_id);    let name = get_user_name(user_id).ok_or_else(|| error_msg.clone());    // أو حتى أفضل:    let name = get_user_name(user_id).ok_or_else(|| format!("User {} not found", user_id));    println!("{:?}", name);}

نصيحة: استخدم ok_or إذا كانت رسالة الخطأ ثابتة. استخدم ok_

المدونات

توصيات الكتب