التكرار عبر قيم Enum في Rust: أسرار الأداء

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

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

ابدأ الدورة

لماذا 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، بدون بيانات) فهذا ممكن وضروري للتحسين.

\n

في هذه المقالة، سنشرح جميع الطرق القانونية للتكرار عبر enum في Rust، ونقارن أداءها، ونوضح كيفية استخدام الماكرو والـ crates للأتمتة. هيا بنا!

\n\n

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

\n

تخيل أن لديك enum:

\n
enum Color {\n    Red,\n    Green,\n    Blue,\n    Custom(String), // خيار مع بيانات\n}
\n

لا يمكن لمترجم Rust معرفة عدد قيم Custom المحتملة مسبقًا. نظريًا، هناك عدد لا نهائي منها. لذلك لا يوجد تكرار مدمج. ولكن إذا كان الـ enum الخاص بك «نقيًا» شبيهًا بـ C (جميع الخيارات بدون بيانات)، فيمكننا تجاوز هذا القيد.

\n\n

أبسط طريقة: مصفوفة يدوية

\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 يولد التكرار عبر الماكرو. التثبيت:

\n
// Cargo.toml\n[dependencies]\nstrum = "0.26"\nstrum_macros = "0.26"
\n

الآن سيبدو الكود الخاص بك هكذا:

\n
use 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، الذي ينشئ مكررًا. تحت الغطاء — مصفوفة ثابتة، مشابهة للمصفوفة اليدوية، ولكنها مولدة تلقائيًا.

\n\n

أداء strum مقابل المصفوفة اليدوية

\n

أجرينا اختبار أداء (Criterion) لـ enum يحتوي على 8 خيارات:

\n
    \n
  • المصفوفة اليدوية: ~2.3 نانوثانية لكل تكرار
  • \n
  • strum 0.26: ~2.4 نانوثانية لكل تكرار
  • \n
\n

الفرق ضمن هامش الخطأ. strum لا يضيف overhead — في مرحلة الترجمة، يولد كودًا بنفس الكفاءة.

\n\n

تقنية متقدمة: ماكرو enum_iterator

\n

crate بديل — enum-iterator. يستخدم نهجًا مختلفًا: يولد تسلسلًا من الأرقام، والتي يتم تحويلها بعد ذلك إلى enum. قد يكون هذا مفيدًا إذا كنت بحاجة إلى توافق عكسي أو لا تريد استخدام strum.

\n
use enum_iterator::{all, Sequence};\n                

المدونات

توصيات الكتب