الـ Generics في Rust: لماذا هي ضرورية وكيفية استخدامها
الـ Generics (التعميمات) هي أحد المفاهيم الأساسية في Rust، والتي تتيح كتابة كود مرن وقابل لإعادة الاستخدام وآمن. إذا كنت قد بدأت للتو في تعلم Rust، فإن فهم الـ Generics سيفتح لك الطريق لكتابة مكتبات وتطبيقات فعالة. في هذه المقالة، سنشرح ما هي الـ Generics، ولماذا هي ضرورية، وكيفية تطبيقها عمليًا.
ما هي الـ Generics ولماذا هي ضرورية؟
تخيل أنك بحاجة لكتابة دالة تجد العنصر الأكبر في مصفوفة. للأرقام هناك تطبيق واحد، وللنصوص تطبيق آخر. لتجنب نسخ الكود لكل نوع، تقدم Rust البرمجة العامة باستخدام Generics.
تسمح الـ Generics بتعريف هياكل، دوال، تعدادات، أو دوال تعمل مع أي نوع بيانات، دون الارتباط بنوع محدد. يقوم المترجم أثناء الترجمة باستبدال النوع الحقيقي - وتسمى هذه العملية بالتجسيد الأحادي (Monomorphization).
بدون الـ Generics، كنت ستكتب:
fn max_i32(list: &[i32]) -> i32 { let mut max = list[0]; for &item in list { if item > max { max = item; } } max}
fn max_f64(list: &[f64]) -> f64 { let mut max = list[0]; for &item in list { if item > max { max = item; } } max}مع الـ Generics، يتحول هذا إلى دالة واحدة شاملة.
أساسيات استخدام الـ Generics
الدوال العامة (Generic Functions)
لجعل الدالة عامة، تحتاج إلى تحديد معامل النوع بين قوسين زاوية <T>:
fn max<T>(list: &[T]) -> T { let mut max = &list[0]; for item in list { if item > max { max = item; } } *max}لكن هذا الكود لن يتم ترجمته - سيخبرك المترجم أن النوع T لا يطبق الـ Trait PartialOrd. وهنا يأتي دور قيود الـ Traits.
الهياكل والتعدادات العامة (Generic Structs and Enums)
يمكن أن تكون الهياكل عامة أيضًا:
struct Point<T> { x: T, y: T,}
fn main() { let int_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.5, y: 3.8 };}يمكن تحديد عدة معاملات للنوع:
struct Pair<T, U> { first: T, second: U,}التعدادات تدعم أيضًا الـ Generics - تذكر Option<T> أو Result<T, E> من المكتبة القياسية.
الدوال العامة (Generic Methods)
بالنسبة للهياكل ذات الـ Generics، يمكن تعريف دوال:
impl<T> Point<T> { fn x(&self) -> &T { &self.x }}أو فقط لنوع محدد:
impl Point<f64> { fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() }}قيود الـ Traits (Trait Bounds)
لاستخدام عمليات المقارنة أو الجمع أو دوال أخرى، تحتاج إلى تحديد الـ Traits التي يطبقها النوع. لنعد إلى دالة max:
fn max<T: PartialOrd>(list: &[T]) -> T { let mut max = &list[0]; for item in list { if item > max { max = item; } } *max}الآن يتم ترجمة الكود - لقد قلنا أن T يجب أن يدعم الترتيب الجزئي (PartialOrd).
يمكن دمج الـ Traits باستخدام +:
fn clone_and_compare<T: Clone + PartialOrd>(a: T, b: T) -> bool { a.clone() > b.clone()}أو استخدام صيغة where لتحسين القراءة:
fn some_function<T, U>(t: &T, u: &U) -> i32where T: