Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

الأنواع العامة والسمات وفترات الحياة

تمتلك كل لغة برمجة أدوات للتعامل بفعالية مع تكرار المفاهيم. في لغة Rust، إحدى هذه الأدوات هي الأنواع العامة (generics): وهي بدائل مجردة للأنواع الملموسة (concrete types) أو الخصائص الأخرى. يمكننا التعبير عن سلوك generics أو كيفية ارتباطها بـ generics أخرى دون معرفة ما سيحل محلها عند تصريف (compiling) وتشغيل الكود.

يمكن للدوال أن تأخذ معاملات من نوع عام (generic type)، بدلاً من نوع ملموس (concrete type) مثل i32 أو String بنفس الطريقة التي تأخذ بها معاملات بقيم غير معروفة لتشغيل نفس الكود على قيم ملموسة متعددة. في الواقع، لقد استخدمنا بالفعل generics في الفصل السادس مع Option<T>، وفي الفصل الثامن مع Vec<T> و HashMap<K, V>، وفي الفصل التاسع مع Result<T, E>. في هذا الفصل، ستستكشف كيفية تعريف الأنواع والدوال والطرق (methods) الخاصة بك باستخدام generics!

أولاً، سنراجع كيفية استخراج دالة لتقليل تكرار الكود. سنستخدم بعد ذلك نفس التقنية لإنشاء دالة عامة (generic function) من دالتين تختلفان فقط في أنواع معاملاتهما. سنشرح أيضاً كيفية استخدام generic types في تعريفات الهياكل (structs) والتعدادات (enums).

بعد ذلك، ستتعلم كيفية استخدام السمات (traits) لتعريف السلوك بطريقة عامة. يمكنك دمج traits مع generic types لتقييد generic type لقبول تلك الأنواع التي تمتلك سلوكاً معيناً فقط، بدلاً من قبول أي نوع فحسب.

أخيراً، سنناقش فترات الحياة (lifetimes): وهي نوع من generics تزود المترجم (compiler) بمعلومات حول كيفية ارتباط المراجع (references) ببعضها البعض. تسمح لنا lifetimes بإعطاء compiler معلومات كافية حول القيم المستعارة (borrowed values) حتى يتمكن من ضمان أن references ستكون صالحة في مواقف أكثر مما يمكنه فعله بدون مساعدتنا.

إزالة التكرار عن طريق استخراج دالة

تسمح لنا generics باستبدال أنواع محددة بعنصر نائب (placeholder) يمثل أنواعاً متعددة لإزالة تكرار الكود. قبل الغوص في بناء جملة (syntax) الـ generics، دعونا ننظر أولاً في كيفية إزالة التكرار بطريقة لا تتضمن generic types عن طريق استخراج دالة تستبدل قيمًا محددة بـ placeholder يمثل قيمًا متعددة. بعد ذلك، سنطبق نفس التقنية لاستخراج generic function! من خلال النظر في كيفية التعرف على الكود المكرر الذي يمكنك استخراجه إلى دالة، ستبدأ في التعرف على الكود المكرر الذي يمكنه استخدام generics.

سنبدأ بالبرنامج القصير في القائمة 10-1 الذي يجد أكبر رقم في قائمة.

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {largest}");
    assert_eq!(*largest, 100);
}

نقوم بتخزين قائمة من الأعداد الصحيحة في المتغير number_list ونضع مرجعاً (reference) لأول رقم في القائمة في متغير يسمى largest. ثم نقوم بالتكرار (iterate) عبر جميع الأرقام في القائمة، وإذا كان الرقم الحالي أكبر من الرقم المخزن في largest نقوم باستبدال reference في ذلك المتغير. ومع ذلك، إذا كان الرقم الحالي أقل من أو يساوي أكبر رقم شوهد حتى الآن، فلا يتغير المتغير، وينتقل الكود إلى الرقم التالي في القائمة. بعد النظر في جميع الأرقام في القائمة، يجب أن يشير largest إلى أكبر رقم، وهو في هذه الحالة 100.

لقد كُلفنا الآن بالعثور على أكبر رقم في قائمتين مختلفتين من الأرقام. للقيام بذلك، يمكننا اختيار تكرار الكود الموجود في القائمة 10-1 واستخدام نفس المنطق في مكانين مختلفين في البرنامج، كما هو موضح في القائمة 10-2.

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {largest}");

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {largest}");
}

على الرغم من أن هذا الكود يعمل، إلا أن تكرار الكود أمر ممل وعرضة للخطأ. كما يتعين علينا تذكر تحديث الكود في أماكن متعددة عندما نريد تغييره.

للقضاء على هذا التكرار، سننشئ تجريداً (abstraction) من خلال تعريف دالة تعمل على أي قائمة من الأعداد الصحيحة يتم تمريرها كمعامل (parameter). يجعل هذا الحل كودنا أكثر وضوحاً ويسمح لنا بالتعبير عن مفهوم العثور على أكبر رقم في قائمة بشكل مجرد.

في القائمة 10-3، نستخرج الكود الذي يجد أكبر رقم في دالة تسمى largest. ثم نستدعي الدالة للعثور على أكبر رقم في القائمتين من القائمة 10-2. يمكننا أيضاً استخدام الدالة على أي قائمة أخرى من قيم i32 قد تكون لدينا في المستقبل.

fn largest(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {result}");
    assert_eq!(*result, 100);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let result = largest(&number_list);
    println!("The largest number is {result}");
    assert_eq!(*result, 6000);
}

تمتلك الدالة largest معاملًا يسمى list يمثل أي شريحة (slice) ملموسة من قيم i32 قد نمررها إلى الدالة. ونتيجة لذلك، عندما نستدعي الدالة، يتم تشغيل الكود على القيم المحددة التي نمررها.

باختصار، إليك الخطوات التي اتخذناها لتغيير الكود من القائمة 10-2 إلى القائمة 10-3:

  1. تحديد الكود المكرر.
  2. استخراج الكود المكرر في جسم الدالة، وتحديد المدخلات وقيم الإرجاع (return values) لهذا الكود في توقيع الدالة (function signature).
  3. تحديث مثيلي الكود المكرر لاستدعاء الدالة بدلاً من ذلك.

بعد ذلك، سنستخدم هذه الخطوات نفسها مع generics لتقليل تكرار الكود. بنفس الطريقة التي يمكن بها لجسم الدالة أن يعمل على list مجردة بدلاً من قيم محددة، تسمح generics للكود بالعمل على أنواع مجردة.

على سبيل المثال، لنفترض أن لدينا دالتين: واحدة تجد أكبر عنصر في slice من قيم i32 وأخرى تجد أكبر عنصر في slice من قيم char. كيف سنزيل هذا التكرار؟ لنكتشف ذلك!