الملحق ج: السمات القابلة للاشتقاق (Appendix C: Derivable Traits)
في أماكن مختلفة من الكتاب، ناقشنا سمة derive ، والتي يمكنك تطبيقها على تعريف هيكل (struct) أو تعداد (enum). تولد سمة derive كوداً يقوم بتنفيذ سمة (trait) مع تنفيذها الافتراضي الخاص على النوع الذي قمت بتمييزه بصيغة derive.
في هذا الملحق، نقدم مرجعاً لجميع الـ traits في المكتبة القياسية التي يمكنك استخدامها مع derive. يغطي كل قسم ما يلي:
- ما هي المعاملات (operators) والطرق (methods) التي سيمكنها اشتقاق هذه الـ trait.
- ماذا يفعل تنفيذ الـ trait الذي توفره
derive. - ماذا يعني تنفيذ الـ trait بالنسبة للنوع.
- الشروط التي يسمح لك فيها أو لا يسمح لك بتنفيذ الـ trait.
- أمثلة على العمليات التي تتطلب الـ trait.
إذا كنت تريد سلوكاً مختلفاً عن ذلك الذي توفره سمة derive ، فراجع توثيق المكتبة القياسية لكل trait للحصول على تفاصيل حول كيفية تنفيذها يدوياً.
الـ traits المدرجة هنا هي الوحيدة المعرفة بواسطة المكتبة القياسية التي يمكن تنفيذها على أنواعك باستخدام derive. الـ traits الأخرى المعرفة في المكتبة القياسية ليس لها سلوك افتراضي منطقي، لذا فالأمر متروك لك لتنفيذها بالطريقة التي تناسب ما تحاول تحقيقه.
مثال على trait لا يمكن اشتقاقها هو Display ، والتي تتعامل مع التنسيق للمستخدمين النهائيين. يجب عليك دائماً التفكير في الطريقة المناسبة لعرض نوع ما للمستخدم النهائي. ما هي أجزاء النوع التي يجب السماح للمستخدم النهائي برؤيتها؟ ما هي الأجزاء التي سيجدونها ذات صلة؟ ما هو تنسيق البيانات الذي سيكون أكثر صلة بهم؟ لا يمتلك مترجم (compiler) Rust هذه الرؤية، لذا لا يمكنه توفير سلوك افتراضي مناسب لك.
قائمة الـ derivable traits المقدمة في هذا الملحق ليست شاملة: يمكن للمكتبات تنفيذ derive لـ traits الخاصة بها، مما يجعل قائمة الـ traits التي يمكنك استخدام derive معها مفتوحة تماماً. يتضمن تنفيذ derive استخدام ماكرو إجرائي (procedural macro)، والذي تمت تغطيته في قسم “ماكروهات derive المخصصة” في الفصل العشرين.
Debug لمخرجات المبرمج (Debug for Programmer Output)
تمكن سمة Debug تنسيق التصحيح (debug formatting) في سلاسل التنسيق، والتي تشير إليها بإضافة :? داخل العناصر النائبة {}.
تسمح لك سمة Debug بطباعة مثيلات (instances) من نوع ما لأغراض التصحيح، بحيث يمكنك أنت والمبرمجون الآخرون الذين يستخدمون نوعك فحص instance في نقطة معينة من تنفيذ البرنامج.
سمة Debug مطلوبة، على سبيل المثال، في استخدام ماكرو assert_eq!. يطبع هذا الماكرو قيم الـ instances المعطاة كـ arguments إذا فشل تأكيد المساواة حتى يتمكن المبرمجون من رؤية سبب عدم تساوي الـ instances الاثنين.
PartialEq و Eq لمقارنات المساواة (PartialEq and Eq for Equality Comparisons)
تسمح لك سمة PartialEq بمقارنة instances من نوع ما للتحقق من المساواة وتمكن من استخدام المعاملات == و !=.
يؤدي اشتقاق PartialEq إلى تنفيذ طريقة eq. عند اشتقاق PartialEq على الـ structs، يكون الـ instances متساويين فقط إذا كانت جميع الحقول (fields) متساوية، ولا يكون الـ instances متساويين إذا كان أي من الـ fields غير متساوٍ. عند الاشتقاق على الـ enums، يكون كل متغير (variant) مساوياً لنفسه وغير مساوٍ للـ variants الأخرى.
سمة PartialEq مطلوبة، على سبيل المثال، مع استخدام ماكرو assert_eq! ، الذي يحتاج إلى القدرة على مقارنة اثنين من instances لنوع ما من أجل المساواة.
سمة Eq ليس لها طرق. الغرض منها هو الإشارة إلى أنه لكل قيمة من النوع المميز، تكون القيمة مساوية لنفسها. لا يمكن تطبيق سمة Eq إلا على الأنواع التي تنفذ أيضاً PartialEq ، على الرغم من أنه لا يمكن لجميع الأنواع التي تنفذ PartialEq تنفيذ Eq. أحد الأمثلة على ذلك هو أنواع الأرقام ذات الفاصلة العائمة (floating-point number types): ينص تنفيذ أرقام الفاصلة العائمة على أن اثنين من instances لقيمة “ليس رقماً” (NaN) غير متساويين مع بعضهما البعض.
مثال على متى تكون Eq مطلوبة هو للمفاتيح (keys) في HashMap<K, V> بحيث يمكن للـ HashMap<K, V> معرفة ما إذا كان مفتاحان هما نفس الشيء.
PartialOrd و Ord لمقارنات الترتيب (PartialOrd and Ord for Ordering Comparisons)
تسمح لك سمة PartialOrd بمقارنة instances من نوع ما لأغراض الفرز (sorting). يمكن استخدام النوع الذي ينفذ PartialOrd مع المعاملات < و > و <= و >=. يمكنك فقط تطبيق سمة PartialOrd على الأنواع التي تنفذ أيضاً PartialEq.
يؤدي اشتقاق PartialOrd إلى تنفيذ طريقة partial_cmp ، والتي تعيد Option<Ordering> الذي سيكون None عندما لا تنتج القيم المعطاة ترتيباً. مثال على قيمة لا تنتج ترتيباً، على الرغم من إمكانية مقارنة معظم قيم ذلك النوع، هو قيمة NaN للفاصلة العائمة. استدعاء partial_cmp مع أي رقم فاصلة عائمة وقيمة NaN للفاصلة العائمة سيعيد None.
عند الاشتقاق على الـ structs، تقارن PartialOrd بين اثنين من instances من خلال مقارنة القيمة في كل field بالترتيب الذي تظهر به الـ fields في تعريف الـ struct. عند الاشتقاق على الـ enums، تعتبر الـ variants الخاصة بالـ enum المصرح عنها سابقاً في تعريف الـ enum أقل من الـ variants المدرجة لاحقاً.
سمة PartialOrd مطلوبة، على سبيل المثال، لطريقة gen_range من صندوق (crate) الـ rand التي تولد قيمة عشوائية في النطاق المحدد بواسطة تعبير النطاق (range expression).
تسمح لك سمة Ord بمعرفة أنه لأي قيمتين من النوع المميز، سيوجد ترتيب صالح. تنفذ سمة Ord طريقة cmp ، والتي تعيد Ordering بدلاً من Option<Ordering> لأن الترتيب الصالح سيكون ممكناً دائماً. يمكنك فقط تطبيق سمة Ord على الأنواع التي تنفذ أيضاً PartialOrd و Eq (و Eq تتطلب PartialEq). عند الاشتقاق على الـ structs والـ enums، تتصرف cmp بنفس الطريقة التي يتصرف بها التنفيذ المشتق لـ partial_cmp مع PartialOrd.
مثال على متى تكون Ord مطلوبة هو عند تخزين القيم في BTreeSet<T> ، وهو هيكل بيانات يخزن البيانات بناءً على ترتيب فرز القيم.
Clone و Copy لمضاعفة القيم (Clone and Copy for Duplicating Values)
تسمح لك سمة Clone بإنشاء نسخة عميقة (deep copy) من قيمة بشكل صريح، وقد تتضمن عملية المضاعفة تشغيل كود عشوائي ونسخ بيانات الكومة (heap data). راجع قسم “تفاعل المتغيرات والبيانات مع Clone” في الفصل الرابع لمزيد من المعلومات حول Clone.
يؤدي اشتقاق Clone إلى تنفيذ طريقة clone ، والتي عند تنفيذها للنوع بالكامل، تستدعي clone على كل جزء من أجزاء النوع. هذا يعني أن جميع الـ fields أو القيم في النوع يجب أن تنفذ أيضاً Clone لاشتقاق Clone.
مثال على متى تكون Clone مطلوبة هو عند استدعاء طريقة to_vec على شريحة (slice). الـ slice لا يمتلك instances النوع التي يحتوي عليها، ولكن الـ vector المعاد من to_vec سيحتاج إلى امتلاك instances الخاصة به، لذا تستدعي to_vec الطريقة clone على كل عنصر. وبالتالي، يجب أن ينفذ النوع المخزن في الـ slice سمة Clone.
تسمح لك سمة Copy بمضاعفة قيمة عن طريق نسخ البتات (bits) المخزنة على المكدس (stack) فقط؛ لا يلزم وجود كود عشوائي. راجع قسم “بيانات المكدس فقط: Copy” في الفصل الرابع لمزيد من المعلومات حول Copy.
لا تعرف سمة Copy أي طرق لمنع المبرمجين من تحميل تلك الطرق بشكل زائد وانتهاك افتراض عدم تشغيل كود عشوائي. بهذه الطريقة، يمكن لجميع المبرمجين افتراض أن نسخ قيمة سيكون سريعاً جداً.
يمكنك اشتقاق Copy على أي نوع تنفذ جميع أجزائه Copy. يجب أن ينفذ النوع الذي ينفذ Copy أيضاً Clone لأن النوع الذي ينفذ Copy لديه تنفيذ بسيط لـ Clone يؤدي نفس المهمة مثل Copy.
نادراً ما تكون سمة Copy مطلوبة؛ الأنواع التي تنفذ Copy لديها تحسينات متاحة، مما يعني أنك لست مضطراً لاستدعاء clone ، مما يجعل الكود أكثر إيجازاً.
كل شيء ممكن مع Copy يمكنك أيضاً تحقيقه باستخدام Clone ، ولكن الكود قد يكون أبطأ أو يضطر لاستخدام clone في بعض الأماكن.
Hash لربط قيمة بقيمة ذات حجم ثابت (Hash for Mapping a Value to a Value of Fixed Size)
تسمح لك سمة Hash بأخذ instance من نوع ذو حجم عشوائي وربط ذلك الـ instance بقيمة ذات حجم ثابت باستخدام دالة تجزئة (hash function). يؤدي اشتقاق Hash إلى تنفيذ طريقة hash. يجمع التنفيذ المشتق لطريقة hash نتيجة استدعاء hash على كل جزء من أجزاء النوع، مما يعني أن جميع الـ fields أو القيم يجب أن تنفذ أيضاً Hash لاشتقاق Hash.
مثال على متى تكون Hash مطلوبة هو في تخزين الـ keys في HashMap<K, V> لتخزين البيانات بكفاءة.
Default للقيم الافتراضية (Default for Default Values)
تسمح لك سمة Default بإنشاء قيمة افتراضية لنوع ما. يؤدي اشتقاق Default إلى تنفيذ دالة default. يستدعي التنفيذ المشتق لدالة default دالة default على كل جزء من النوع، مما يعني أن جميع الـ fields أو القيم في النوع يجب أن تنفذ أيضاً Default لاشتقاق Default.
تُستخدم دالة Default::default بشكل شائع بالاشتراك مع صيغة تحديث الهيكل (struct update syntax) التي تمت مناقشتها في قسم “إنشاء مثيلات من مثيلات أخرى باستخدام صيغة تحديث الهيكل” في الفصل الخامس. يمكنك تخصيص عدد قليل من fields الهيكل ثم تعيين واستخدام قيمة افتراضية لبقية الـ fields باستخدام ..Default::default().
سمة Default مطلوبة عندما تستخدم الطريقة unwrap_or_default على instances الـ Option<T> ، على سبيل المثال. إذا كان الـ Option<T> هو None ، فستعيد الطريقة unwrap_or_default نتيجة Default::default للنوع T المخزن في الـ Option<T>.