البرمجة العامة في Rust: لماذا لا يمكنك الاستغناء عنها؟
\n\nعندما تبدأ في كتابة كود بلغة Rust، قد تشعر أن نظام الأنواع هو عدوك. المترجم ينتقد كل فاصلة، وكلمة Generic تثير الرهبة. لكن في الحقيقة، الـ generics (الأنواع العامة) هي أفضل صديق لك. فهي تسمح لك بكتابة كود مرن وآمن وقابل لإعادة الاستخدام دون التضحية بالأداء.
في هذا المقال، سنشرح ما هي البرمجة العامة في Rust، وكيفية استخدامها في الدوال، الهياكل، والـ traits. ستكتشف أن البرمجة العامة ليست سحرًا، بل أداة قوية تجعل كودك أنظف وأسرع.
\n\nما هي الـ Generics ولماذا نحتاجها؟
\n\nتخيل أنك تكتب دالة لإيجاد أكبر عنصر في مصفوفة. بدون الـ generics، ستحتاج لكتابة تطبيق منفصل لكل نوع مثل i32 و f64 و String. هذا يستغرق وقتًا طويلاً وينتهك مبدأ DRY (لا تكرر نفسك).
الـ generics تحل هذه المشكلة. فهي تسمح بجعل الأنواع معاملات: تكتب دالة واحدة تعمل مع أي نوع بيانات. المترجم يولد تلقائيًا الإصدارات المناسبة من الكود لكل حالة محددة. هذه العملية تسمى monomorphization.
\n\nصيغة الـ Generics: من البسيط إلى المعقد
\n\n1. الدوال العامة
\n\nلنبدأ بمثال أساسي. لنفترض أننا نريد إنشاء دالة تعيد القيمة التي تمررها (دالة الهوية):
\n\nfn identity<T>(value: T) -> T {\n value\n}\n\nfn main() {\n let n = identity(42);\n let s = identity("hello");\n println!("{}, {}", n, s); // 42, hello\n}\n\nهنا <T> هو معامل النوع. T يمكن أن يكون أي نوع: رقم، نص، هيكل. عند الاستدعاء، المترجم يخمن النوع المناسب. هذا لا يعمل عن طريق التوزيع الديناميكي (كما في Java أو Python)، بل عن طريق توليد كود متخصص أثناء التجميع. لهذا السبب لا تضحي Rust بالأداء.
2. قيود الأنواع (Trait Bounds)
\n\nلكن ماذا لو أردنا ليس فقط تمرير قيمة، بل مثلاً مقارنة رقمين؟ مجرد <T> لن يعمل، لأن عملية المقارنة غير معرفة لجميع الأنواع. نحتاج لتحديد أن T يجب أن يطبق trait PartialOrd:
fn max<T: PartialOrd>(a: T, b: T) -> T {\n if a > b { a } else { b }\n}\n\nfn main() {\n println!("{}", max(10, 20)); // 20\n println!("{}", max(3.14, 2.71)); // 3.14\n}\n\nالكتابة T: PartialOrd هي trait bound. تقول للمترجم: "استخدم فقط الأنواع التي تم تعريف ترتيب المقارنة لها". جرب تمرير نصين إلى max وستعمل، لأن String تطبق PartialOrd. أما إذا مررت هيكلًا بدون تطبيق، فستحصل على خطأ تجميع.
3. قيود متعددة
\n\nأحيانًا تحتاج أن يحقق النوع عدة traits في نفس الوقت. مثلاً، أن يكون قابلاً للمقارنة والاستنساخ:
\n\nfn clone_and_max<T: PartialOrd + Clone>(a: T, b: T) -> T {\n let a_cloned = a.clone();\n let b_cloned = b.clone();\n if a_cloned > b_cloned { a } else { b }\n}\n\nالصيغة T: PartialOrd + Clone تعني أن النوع يجب أن يطبق كلا الـ trait. هذا قوي، لكن لا تفرط في استخدامه: الكثير من القيود يعقد القراءة.
الـ Generics في الهياكل والتعدادات
\n\nالهياكل العامة
\n\nالـ generics تعمل بشكل رائع ليس فقط مع الدوال، بل مع الأنواع المخصصة أيضًا. المثال الكلاسيكي هو هيكل Point الذي يمكنه تخزين إحداثيات من أي نوع:
struct Point<T> {\n x: T,\n y: T,\n}\n\nimpl<T> Point<T> {\n fn new(x: