مقدمة: ما هي Generics ولماذا هي في Rust؟
\nتخيل أنك بحاجة لكتابة دالة تجد أكبر عنصر في قائمة. للأعداد الصحيحة - دالة واحدة، للأعداد العشرية - أخرى، للسلاسل النصية - ثالثة. سيتكرر الكود وسيصبح من الصعب صيانته. في Rust، تحل هذه المشكلة Generics (البرمجة العامة).
\nتسمح Generics بكتابة كود يعمل مع أنواع بيانات مختلفة دون فقدان صرامة الكتابة (strict typing). بدلاً من تكرار الدوال لكل نوع، تكتب نسخة عامة واحدة. في مرحلة الترجمة، يقوم Rust باستبدال النوع المحدد والتحقق من صحته. هذا يوفر:
\n- \n
- إعادة استخدام الكود - دالة واحدة لجميع الأنواع. \n
- الأمان - يتم اكتشاف جميع أخطاء الأنواع في مرحلة الترجمة. \n
- الأداء - بفضل التحويل الأحادي (monomorphization) (يقوم المترجم بإنشاء كود منفصل لكل نوع مستخدم)، يعمل الكود العام بنفس سرعة الكود المتخصص. \n
في هذه المقالة، سنشرح كيفية استخدام Generics في Rust مع أمثلة عملية، بدءاً من الدوال البسيطة وصولاً إلى الهياكل والـ Traits.
\n\n1. Generics في الدوال: الخطوة الأولى نحو العالمية
\nالصيغة والمثال الأساسي
\nلجعل الدالة عامة، تحتاج إلى تحديد معامل النوع بين قوسين زاوية <T>. عادةً ما تستخدم الأحرف الكبيرة: T، U، V. إليك كيف تبدو دالة تعيد القيمة التي تم تمريرها:
fn identity<T>(value: T) -> T {\n value\n}\n\nfn main() {\n let num = identity(42);\n let text = identity("Hello");\n println!("{} {}", num, text); // ستطبع: 42 Hello\n}\nيقوم المترجم باستنتاج النوع بناءً على الوسيطة الممررة. يمكنك أيضاً تحديد النوع بشكل صريح: identity::<i32>(42).
القيود: الـ Traits كحدود للأنواع
\nماذا لو أردنا مقارنة القيم؟ لا يمكنك ببساطة كتابة value1 > value2 - ليست كل الأنواع تدعم المقارنة. تحتاج إلى تحديد أن النوع يجب أن ينفذ الـ Trait PartialOrd:
fn largest<T: PartialOrd>(list: &[T]) -> &T {\n let mut largest = &list[0];\n for item in list {\n if item > largest {\n largest = item;\n }\n }\n largest\n}\n\nfn main() {\n let numbers = vec![34, 50, 25, 100, 65];\n println!("أكبر رقم: {}", largest(&numbers));\n\n let chars = vec!['y', 'm', 'a', 'q'];\n println!("أكبر حرف: {}", largest(&chars));\n}\nهنا T: PartialOrd هو قيود الـ Trait. يضمن أن عامل المقارنة محدد للنوع T. بدون هذا القيد، لن يتم ترجمة الكود.
2. Generics في الهياكل والتعدادات
\nالهياكل ذات الحقول العامة
\nتعمل Generics بشكل رائع أيضاً مع الأنواع المخصصة. على سبيل المثال، يمكن للهيكل Point تخزين إحداثيات من أي نوع:
struct Point<T> {\n x: T,\n y: T,\n}\n\nfn main() {\n let integer_point = Point { x: 5, y: 10 };\n let float_point = Point { x: 1.0, y: 4.0 };\n // let mixed = Point { x: 5, y: 4.0 }; // خطأ! يجب أن يكون كلا الحقلين من نفس النوع\n}\nإذا كنت بحاجة إلى أن تكون الحقول من أنواع مختلفة، استخدم عدة معاملات:
\nstruct Point<T, U> {\n x: T,\n y: U,\n}\n\nfn main() {\n let mixed = Point { x: 5, y: 4.0 }; // يعمل الآن\n}\n\nالتعدادات مع Generics
\nالتعداد القياسي Option<T> هو مثال ممتاز على التعداد العام. يمكنك إنشاء تعدادك الخاص:
enum Result<T, E> {\n Ok(T),\n Err(E),\n}\n\n// الاستخدام\nfn divide(a: f64, b: f64) -> Result<f64, String> {\n if b