إرجاع المكررات في Rust: أسرار الأداء وتجريدات التكلفة الصفرية

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

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

ابدأ الدورة

لماذا إرجاع مكرر هو أمر صعب

\n\n

هل سبق لك أن كتبت دالة في Rust يجب أن تعيد مجموعة تمت معالجتها عبر سلسلة من المكررات؟ إذا كان الأمر كذلك، فمن المحتمل أنك واجهت السؤال: "أي نوع يجب أن أعيد؟" يبدو من المنطقي كتابة fn get_data() -> Iterator، لكن المترجم يعطي خطأً فوراً: "the trait `Iterator` cannot be made into an object".

\n\n

هذه المشكلة شائعة جداً لدرجة أن سؤال Stack Overflow "ما هي الطريقة الصحيحة لإرجاع Iterator (أو أي trait آخر)؟" حصل على آلاف المشاهدات. وليس من المستغرب - فالاختيار الصحيح لا يؤثر فقط على سهولة الكود، بل على الأداء أيضاً. في هذه المقالة، سنناقش جميع طرق إرجاع المكررات في Rust، إيجابياتها وسلبياتها، والأهم من ذلك - كيف لا تفقد الأداء في أبسط المواقف.

\n\n

المقالة مكتوبة لـ pythonlib.ru - موقع تعليمي حيث لا نخاف من النظر تحت غطاء اللغات الشائعة. Rust، بتجريداتها ذات التكلفة الصفرية، تمنحنا فرصة فريدة لكتابة كود أنيق وسريع في نفس الوقت. ولكن فقط إذا عرفنا كيفية استخدام أدواتها بشكل صحيح.

\n\n

المشكلة: الـ traits ليس لها حجم

\n\n

في Rust، الـ traits هي واجهات، ولكن على عكس Java أو Go، فهي ليست أنواعاً ذات حجم معروف. يحتاج المترجم إلى معرفة مقدار الذاكرة التي تشغلها القيمة المُعادة لتخصيص مساحة على المكدس. وحجم التنفيذ المحدد للمكرر (مثل std::iter::Map أو std::iter::Filter) يمكن أن يكون مختلفاً.

\n\n

لذلك، لا يمكن إرجاع Iterator مباشرة. يجب اختيار أحد الأنماط التالية:

\n\n
    \n
  • الإرسال الثابت — impl Iterator (إرجاع نوع محدد)
  • \n
  • الإرسال الديناميكي — Box<dyn Iterator> (يتم محو النوع)
  • \n
  • Enum dispatch — enum خاص لجميع المكررات الممكنة
  • \n
  • الـ Generics — دالة تستقبل وتعيد أنواعاً عامة
  • \n
\n\n

لكل نهج ثمنه. دعنا نناقشها بأمثلة حقيقية.

\n\n

الطريقة 1: impl Iterator - المعيار الذهبي

\n\n

أبسط وأكثر الطرق أداءً هي استخدام impl Trait في النوع المُعاد. هذا سكر نحوي يخبر المترجم: "سأعيد نوعاً محدداً ينفذ الـ trait، لكنك استنتجه بنفسك".

\n\n
fn get_numbers() -> impl Iterator<Item = i32> {\n    (0..10).map(|x| x * 2)\n}\n\nfn main() {\n    for num in get_numbers() {\n        println!("{}", num);\n    }\n}
\n\n

الإيجابيات:

\n
    \n
  • أقصى أداء - إرسال ثابت، لا استدعاءات افتراضية
  • \n
  • يمكن للمترجم تضمين الكود وتطبيق التحسينات
  • \n
  • حجم ثنائي نهائي ضئيل
  • \n
\n\n

السلبيات:

\n
    \n
  • يجب أن يكون النوع المُعاد واحداً - لا يمكن إرجاع Map في حالة و Filter في حالة أخرى
  • \n
  • يمكن أن يكون النوع معقداً جداً (مثل Map<Filter<...>>)، مما يصعّب قراءة أخطاء المترجم
  • \n
  • لا يمكن تخزينه في struct أو vector - فقط في الدوال
  • \n
\n\n

هذه الطريقة مثالية لـ 90% من الحالات. إذا كانت دالتك تعيد دائماً نفس سلسلة المكررات - استخدم impl Iterator دون تردد.

\n\n

الطريقة 2: Box<dyn Iterator> - المرونة على حساب الأداء

\n\n

عندما تحتاج إلى إرجاع أنواع مختلفة من المكررات حسب الشروط (مثلاً عند تحليل وسائط سطر الأوامر)، يأتي الإرسال الديناميكي للإنقاذ عبر Box<dyn Iterator>.

\n\n
fn get_data(use_filter: bool) -> Box<dyn Iterator<Item = i32>> {\n    let data = vec![1, 2, 3, 4, 5];\n    if use_filter {\n                

المدونات

توصيات الكتب