Generics في Rust: لماذا تحتاجها وكيف تستخدمها للمبتدئين

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

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

ابدأ الدورة

مقدمة: ما هي Generics ولماذا هي في Rust؟

\n

تخيل أنك بحاجة لكتابة دالة تجد أكبر عنصر في قائمة. للأعداد الصحيحة - دالة واحدة، للأعداد العشرية - أخرى، للسلاسل النصية - ثالثة. سيتكرر الكود وسيصبح من الصعب صيانته. في Rust، تحل هذه المشكلة Generics (البرمجة العامة).

\n

تسمح Generics بكتابة كود يعمل مع أنواع بيانات مختلفة دون فقدان صرامة الكتابة (strict typing). بدلاً من تكرار الدوال لكل نوع، تكتب نسخة عامة واحدة. في مرحلة الترجمة، يقوم Rust باستبدال النوع المحدد والتحقق من صحته. هذا يوفر:

\n
    \n
  • إعادة استخدام الكود - دالة واحدة لجميع الأنواع.
  • \n
  • الأمان - يتم اكتشاف جميع أخطاء الأنواع في مرحلة الترجمة.
  • \n
  • الأداء - بفضل التحويل الأحادي (monomorphization) (يقوم المترجم بإنشاء كود منفصل لكل نوع مستخدم)، يعمل الكود العام بنفس سرعة الكود المتخصص.
  • \n
\n

في هذه المقالة، سنشرح كيفية استخدام Generics في Rust مع أمثلة عملية، بدءاً من الدوال البسيطة وصولاً إلى الهياكل والـ Traits.

\n\n

1. Generics في الدوال: الخطوة الأولى نحو العالمية

\n

الصيغة والمثال الأساسي

\n

لجعل الدالة عامة، تحتاج إلى تحديد معامل النوع بين قوسين زاوية <T>. عادةً ما تستخدم الأحرف الكبيرة: T، U، V. إليك كيف تبدو دالة تعيد القيمة التي تم تمريرها:

\n
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).

\n\n

القيود: الـ Traits كحدود للأنواع

\n

ماذا لو أردنا مقارنة القيم؟ لا يمكنك ببساطة كتابة value1 > value2 - ليست كل الأنواع تدعم المقارنة. تحتاج إلى تحديد أن النوع يجب أن ينفذ الـ Trait PartialOrd:

\n
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. بدون هذا القيد، لن يتم ترجمة الكود.

\n\n

2. Generics في الهياكل والتعدادات

\n

الهياكل ذات الحقول العامة

\n

تعمل Generics بشكل رائع أيضاً مع الأنواع المخصصة. على سبيل المثال، يمكن للهيكل Point تخزين إحداثيات من أي نوع:

\n
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

إذا كنت بحاجة إلى أن تكون الحقول من أنواع مختلفة، استخدم عدة معاملات:

\n
struct 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> هو مثال ممتاز على التعداد العام. يمكنك إنشاء تعدادك الخاص:

\n
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                

المدونات

توصيات الكتب