مقدمة: لماذا لا يريد المكرر (Iterator) التوقف؟
\n\nتخيل: أنت تكتب برنامجاً بلغة Rust يقوم بمعالجة قائمة من الأرقام — تحليل السلاسل النصية، قراءة الملفات، أو إجراء بعض التحويلات. أنت تستخدم Iterator::map للمرور على كل عنصر. وفجأة! — يتحول أحد العناصر إلى خطأ Result::Err. من المنطقي إيقاف التكرار فوراً وإرجاع هذا الخطأ إلى الأعلى. لكن map يستمر في العمل وكأن شيئاً لم يحدث. لماذا؟ والأهم — كيف يمكن إصلاح ذلك؟
هذا السؤال هو من أكثر الأسئلة شيوعاً على Stack Overflow بين المبتدئين في Rust. وليس من دون سبب. آليات معالجة الأخطاء في Rust مرنة، لكنها تتطلب نهجاً واعياً. في هذه المقالة، سنحلل المشكلة بالتفصيل، وننظر في عدة حلول، ونختار الأكثر أناقة. هيا بنا!
\n\nالمشكلة: map لا يتوقف
\n\nلنبدأ بمثال تقليدي. لنفترض أن لدينا متجهاً من السلاسل النصية التي نريد تحويلها إلى أرقام:
\n\nfn main() {\n let inputs = vec!["42", "hello", "100"];\n\n let result: Vec<Result<i32, std::num::ParseIntError>> =\n inputs.iter().map(|s| s.parse::<i32>()).collect();\n\n println!("{:?}", result);\n}\n\nسيتم تجميع هذا الكود وتنفيذه. لكن ماذا سنحصل؟ متجهاً من Results: [Ok(42), Err(ParseIntError), Ok(100)]. لم يتوقف التكرار عند "hello"، بل استمر. بالنسبة للعديد من المهام، هذا طبيعي — فأنت تريد جمع جميع الأخطاء. لكن ماذا لو كنت بحاجة إلى أول استثناء؟ على سبيل المثال، عند تحميل إعدادات التهيئة: إذا كان أحد المعاملات غير صحيح، فلا فائدة من الاستمرار في التحقق.
المشكلة هي أن map هو تحويل كسول. إنه لا يعرف ما هو "التوقف". إنه يقول ببساطة: "أعطني عنصراً، سأطبق الدالة، وأرجع النتيجة". كل التحكم في الإيقاف والاستمرار يقع على عاتق مستهلك المكرر (collect، for، fold، إلخ). و collect بالنسبة لـ Vec يستمر ببساطة حتى النهاية.
الحل 1: collect مع FromIterator (الأكثر شيوعاً)
\n\nالإجابة الأكثر شيوعاً على Stack Overflow هي استخدام collect::<Result<Vec<_>, _>>(). تعرف Rust أنه يمكن جمع مكرر من Results في Result واحد يحتوي على متجه بداخله. إذا تمت مصادفة Err، يتوقف التجميع ويتم إرجاع هذا الخطأ.
fn main() -> Result<(), std::num::ParseIntError> {\n let inputs = vec!["42", "hello", "100"];\n\n let result: Vec<i32> =\n inputs.iter()\n .map(|s| s.parse::<i32>())\n .collect::<Result<Vec<_>, _>>()?; // <-- سحر!\n\n println!("{:?}", result);\n Ok(())\n}\n\nكيف يعمل هذا؟ النوع Result<Vec<i32>, ParseIntError> يطبق الخاصية FromIterator. عندما يرى collect قيمة Err، فإنه يعيدها فوراً، دون معالجة العناصر المتبقية. إذا كان كل شيء على ما يرام — تحصل على Ok(متجه). هذا قصير، اصطلاحي، وفعال.
العيب: تفقد معلومات حول العنصر الذي حدث فيه الخطأ (إذا لم تقم بتغليفه في نوع خاص بك). لكن بالنسبة للنصوص البرمجية السريعة — هذا هو الحل الأمثل.
\n\nالحل 2: filter_map لتجاهل الأخطاء
\n\nأحياناً لا تريد التوقف، بل تريد ببساطة تجاهل العناصر الفاشلة. على سبيل المثال، عد السلاسل الصالحة فقط. لهذا، filter_map مثالي:
fn main() {\n let inputs = vec!["42", "hello", "100", "abc", "7"];\n\n let numbers: Vec<i32> = inputs.iter()\n .filter_map(|s| s.parse::<i32>().ok()) // .ok() يحول Err إلى None\n .collect();\n\n println!("{:?}", numbers); // [42, 100, 7]\n}\n\nfilter_map يأخذ