الأدوية العامة (Generics) في Rust: لماذا هي ضرورية وكيفية استخدامها

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

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

ابدأ الدورة

مقدمة إلى Generics في Rust



البرمجة العامة (generics) هي أحد المفاهيم الأساسية في Rust، مما يسمح بكتابة كود مرن وقابل لإعادة الاستخدام وآمن. إذا كنت تنتقل إلى Rust من لغات أخرى (مثل C++ أو Java)، فمن المحتمل أنك واجهت القوالب أو الأدوية. في Rust، تعمل generics بشكل مشابه، ولكن مع تركيز خاص على الأداء وسلامة الذاكرة.



لماذا نحتاج إلى generics؟ تخيل أنك بحاجة إلى كتابة دالة تجد العنصر الأكبر في قائمة. بدون generics، سيتعين عليك كتابة نسخة منفصلة لكل من i32 و f64 و String وكل نوع مخصص. مع generics، تكتب دالة واحدة تعمل مع أي نوع يدعم المقارنة.



في هذه المقالة، سنشرح ما هي generics في Rust، وكيفية استخدامها مع الدوال والهياكل والـ traits، بالإضافة إلى أمثلة عملية.



أساسيات Generics: الدوال والهياكل



الدوال العامة



أبسط طريقة للتعرف على generics هي كتابة دالة عامة. يستخدم بناء الجملة الأقواس الزاوية <T>، حيث T هو معامل النوع (type parameter).



fn largest<T>(list: &[T]) -> &T {    let mut largest = &list[0];    for item in list {        if item > largest {            largest = item;        }    }    largest}

fn main() { let numbers = vec![34, 50, 25, 100, 65]; let result = largest(&numbers); println!("الرقم الأكبر: {}", result);

let chars = vec!['y', 'm', 'a', 'q']; let result = largest(&chars); println!("الحرف الأكبر: {}", result);}


لكن هذا الكود لن يتم تجميعه! يتطلب Rust أن يقوم النوع T بتطبيق الـ trait PartialOrd (للمقارنة). سنناقش القيود (trait bounds) لاحقًا، لكن في الوقت الحالي تذكر: generics تسمح بالتجريد عن النوع المحدد، ولكن يجب أن يعرف المترجم العمليات المسموح بها.



الهياكل العامة



تعمل generics ليس فقط مع الدوال، ولكن أيضًا مع أنواع البيانات المخصصة. على سبيل المثال، نقطة ذات إحداثيات يمكنها تخزين أعداد صحيحة أو أعداد عشرية:



struct Point<T> {    x: T,    y: T,}

fn main() { let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 }; println!("النقطة الصحيحة: ({}, {})", integer_point.x, integer_point.y); println!("النقطة العشرية: ({}, {})", float_point.x, float_point.y);}


يمكن استخدام عدة معاملات نوع إذا كانت الحقول من أنواع مختلفة:



struct Pair<T, U> {    first: T,    second: U,}

fn main() { let pair = Pair { first: 42, second: "الإجابة" }; println!("الزوج: {} و {}", pair.first, pair.second);}


Generics مع الـ Traits والقيود (Trait Bounds)



ما هي trait bounds؟



Trait bounds هي طريقة لإخبار المترجم بأن النوع العام يجب أن يطبق traits معينة. بدونها، لا يمكننا استدعاء دوال على قيمة عامة. لنعد إلى الدالة largest ونصلحها:



use std::cmp::PartialOrd;

fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest}

fn main() { let numbers = vec![34, 50, 25, 100, 65]; println!("الأكبر: {}", largest(&numbers));}


الآن تقبل الدالة فقط الأنواع التي تدعم المقارنة (PartialOrd).



قيود متعددة



يمكنك تحديد عدة traits باستخدام +. على سبيل المثال، يجب أن يكون النوع قابلاً للمقارنة والاستنساخ في نفس الوقت:



use std::fmt::Display;

fn print_and_compare

المدونات

توصيات الكتب