التأمل والاستبطان في Rust: كيف تنظر داخل الكود
\n\nعندما انتقلت لأول مرة من Python إلى Rust، كان يقلقني سؤال واحد: "أين هي دالتي المفضلة type() و isinstance()؟". في Python اعتدنا أن التأمل (Reflection) هو الأساس، والهواء الذي يتنفسه كل سكريبت. أما في Rust فالأمر مختلف تمامًا. لكن لا تتعجل في الإحباط! التأمل والاستبطان في Rust موجودان، لكنهما يبدوان مختلفين عن اللغات الديناميكية. واليوم سنفهم كيف نجعلهما يجعلان الكود أكثر مرونة وأمانًا وحتى سحرًا.
التأمل (Reflection) هو قدرة البرنامج على تحليل وتعديل هيكله أثناء التنفيذ. الاستبطان (Introspection) هو أخته الأكثر تواضعًا، التي "تنظر" فقط ولا تلمس. في Rust يمكننا معرفة نوع المتغير، والتحقق مما إذا كانت البنية (Struct) تنفذ سمة (Trait) معينة، أو حتى استدعاء دالة بشكل ديناميكي. وكل هذا دون المساس بالأداء والأمان اللذين أحببنا هذه اللغة من أجلهما.
\n\nلماذا نحتاج التأمل في Rust؟
\n\nتخيل أنك تكتب مكتبة لتسلسل البيانات (Serialization). تريدها أن تعمل مع أي بنية يحددها المستخدم. أو ربما تقوم بإنشاء نظام إضافات (Plugins) حيث تقوم بتحميل الكود أثناء التنفيذ. أو تكتب إطار عمل للاختبارات يجد تلقائيًا جميع الاختبارات في الكود. في كل هذه الحالات، لا يمكنك الاستغناء عن التأمل.
\n\nفي Rust، غالبًا ما يتم تنفيذ التأمل من خلال السمات (Traits) والماكروات والإمكانيات الخاصة للمكتبة القياسية. دعنا نلقي نظرة على أمثلة محددة.
\n\nاستبطان الأنواع باستخدام std::any
\n\nأبسط طريقة للنظر داخل نوع المتغير هي استخدام وحدة std::any. توفر سمة Any التي تسمح بالعمل مع الأنواع كبيانات.
مثال: معرفة نوع المتغير
\n\nuse std::any::{Any, TypeId};\n\nfn print_type_of<T: Any>(_: &T) {\n let type_name = std::any::type_name::<T>();\n println!("النوع: {}", type_name);\n}\n\nfn main() {\n let x = 42;\n let y = "hello";\n let z = vec![1, 2, 3];\n \n print_type_of(&x); // النوع: i32\n print_type_of(&y); // النوع: &str\n print_type_of(&z); // النوع: Vec<i32>\n}\n\nالدالة type_name تُرجع تمثيلًا نصيًا للنوع. هذا ليس تأملًا كاملاً، لكنه ممتاز لتصحيح الأخطاء والتسجيل. لاحظ: سمة Any مطبقة لجميع الأنواع ذات العمر 'static، أي تقريبًا لجميع البنيات الخاصة بك.
مقارنة الأنواع عبر TypeId
\n\nماذا لو احتجنا التحقق مما إذا كانت القيمة تنتمي إلى نوع معين؟ لهذا نستخدم TypeId:
use std::any::{Any, TypeId};\n\nfn is_string(value: &dyn Any) -> bool {\n value.type_id() == TypeId::of::<String>()\n}\n\nfn main() {\n let s = String::from("مرحباً");\n let n = 100i32;\n \n println!("هل هذه سلسلة نصية؟ {}", is_string(&s)); // true\n println!("هل هذه سلسلة نصية؟ {}", is_string(&n)); // false\n}\n\nهذا يشبه أكثر الاستبطان المألوف من اللغات الديناميكية. يمكننا تمرير &dyn Any كحاوية عامة والتحقق من محتواها.
استبطان السمات (Trait Introspection): التحقق من تنفيذ الدوال
\n\nأحيانًا نحتاج معرفة ما إذا كان النوع ينفذ سمة معينة. في Rust لا توجد آلية مباشرة لذلك أثناء التنفيذ، لكن يمكننا تجاوز هذا القيد باستخدام الماكروات أو سمات العلامات الخاصة.
\n\nمثال: التحقق من تنفيذ سمة
\n\ntrait Speak {\n fn speak(&self) -> String;\n}\n\nstruct Dog;\nstruct Cat;\n\nimpl Speak for Dog {\n fn speak(&self) -> String {\n "نباح!".to_string()\n }\n}\n\n// استخدام ماكرو للتحقق\nmacro_rules! impl_speak_check {\n ($t:ty) => {\n impl $t {\n fn can_speak(&self) -> bool {\n