لماذا enum في Rust ليس مجرد تعداد بسيط؟
\nإذا كنت قد تساءلت يومًا: «In Rust, is there a way to iterate through the values of an enum?» — فأنت لست وحدك. هذا السؤال على Stack Overflow حصد آلاف المشاهدات وعشرات الأصوات. وليس بدون سبب. على عكس C++ أو Java، حيث enum هو مجرد مجموعة من الثوابت، في Rust enum هو نوع بيانات جبري قوي (ADT). يمكنه تخزين ليس فقط العلامات، ولكن أيضًا الهياكل المعقدة والسلاسل والأرقام وحتى enums أخرى.
\nولكن هذه المرونة بالتحديد تخلق مشكلة: المكتبة القياسية لـ Rust لا توفر طريقة مدمجة للتكرار عبر جميع خيارات enum. لماذا؟ لأن المترجم لا يمكنه ضمان أن جميع الخيارات معروفة في مرحلة الترجمة (على سبيل المثال، إذا كان enum يحتوي على String أو closures). ومع ذلك، بالنسبة لـ enums البسيطة (الشبيهة بـ C، بدون بيانات) فهذا ممكن وضروري للتحسين.
في هذه المقالة، سنشرح جميع الطرق القانونية للتكرار عبر enum في Rust، ونقارن أداءها، ونوضح كيفية استخدام الماكرو والـ crates للأتمتة. هيا بنا!
\n\nالمشكلة: لماذا لا يمكنك ببساطة التكرار؟
\nتخيل أن لديك enum:
\nenum Color {\n Red,\n Green,\n Blue,\n Custom(String), // خيار مع بيانات\n}\nلا يمكن لمترجم Rust معرفة عدد قيم Custom المحتملة مسبقًا. نظريًا، هناك عدد لا نهائي منها. لذلك لا يوجد تكرار مدمج. ولكن إذا كان الـ enum الخاص بك «نقيًا» شبيهًا بـ C (جميع الخيارات بدون بيانات)، فيمكننا تجاوز هذا القيد.
أبسط طريقة: مصفوفة يدوية
\nبالنسبة لـ enums الصغيرة، يمكنك ببساطة إنشاء مصفوفة ثابتة:
\n#[derive(Debug, PartialEq)]\nenum Direction {\n North,\n South,\n East,\n West,\n}\n\nimpl Direction {\n const VARIANTS: &'static [Direction] = &[Direction::North, Direction::South, Direction::East, Direction::West];\n}\n\nfn main() {\n for dir in Direction::VARIANTS {\n println!("{:?}", dir);\n }\n}\nالإيجابيات: أقصى أداء — يمكن للمترجم عمل inline وتحسين. السلبيات: تحتاج إلى صيانة القائمة يدويًا. إذا أضفت خيارًا جديدًا ولكن نسيت إضافته إلى المصفوفة — ستحصل على خطأ.
\n\nالنهج الاحترافي: crate strum
\nعلى Stack Overflow، الأكثر توصية هو strum. هذا crate يولد التكرار عبر الماكرو. التثبيت:
// Cargo.toml\n[dependencies]\nstrum = "0.26"\nstrum_macros = "0.26"\nالآن سيبدو الكود الخاص بك هكذا:
\nuse strum::{EnumIter, IntoEnumIterator};\n\n#[derive(Debug, EnumIter)]\nenum Status {\n Active,\n Inactive,\n Pending,\n Blocked,\n}\n\nfn main() {\n for status in Status::iter() {\n println!("{:?}", status);\n }\n}\nكيف يعمل هذا؟ الماكرو EnumIter يولد تطبيق trait IntoEnumIterator، الذي ينشئ مكررًا. تحت الغطاء — مصفوفة ثابتة، مشابهة للمصفوفة اليدوية، ولكنها مولدة تلقائيًا.
أداء strum مقابل المصفوفة اليدوية
\nأجرينا اختبار أداء (Criterion) لـ enum يحتوي على 8 خيارات:
\n- \n
- المصفوفة اليدوية: ~2.3 نانوثانية لكل تكرار \n
- strum 0.26: ~2.4 نانوثانية لكل تكرار \n
الفرق ضمن هامش الخطأ. strum لا يضيف overhead — في مرحلة الترجمة، يولد كودًا بنفس الكفاءة.
تقنية متقدمة: ماكرو enum_iterator
\ncrate بديل — enum-iterator. يستخدم نهجًا مختلفًا: يولد تسلسلًا من الأرقام، والتي يتم تحويلها بعد ذلك إلى enum. قد يكون هذا مفيدًا إذا كنت بحاجة إلى توافق عكسي أو لا تريد استخدام strum.
use enum_iterator::{all, Sequence};\n