البرمجة

السمات في لغة رست

السمات Traits في لغة رست Rust: البنية، المفاهيم، والاستخدامات المتقدمة

تُعد السمات (Traits) في لغة رست (Rust) من الركائز الأساسية التي تُمكّن اللغة من تقديم برمجة آمنة، مرنة، وقابلة للتوسعة دون التضحية بالكفاءة أو الأداء. تُستخدم السمات لتحديد السلوكيات التي يمكن أن تشاركها أنواع مختلفة من البيانات (Structs أو Enums) بطريقة تشبه إلى حد ما الواجهات (Interfaces) في لغات أخرى مثل Java أو C#. ومع ذلك، فإن السمات في رست تتجاوز الحدود التقليدية لهذه المفاهيم عبر تكاملها العميق مع نظام الملكية (Ownership) والتعدد الأشكال (Polymorphism) المستند إلى الخصائص.


تعريف السمات (Traits)

في جوهرها، السمة (Trait) هي مجموعة من التواقيع (Signatures) لدوال لا تحتوي على تنفيذ (أو تحتوي على تنفيذ افتراضي) تُستخدم لتحديد سلوك مشترك يمكن لأنواع متعددة أن تنفذه. تُستخدم الكلمة المفتاحية trait لتعريف سمة جديدة.

rust
trait Speak { fn speak(&self); }

في المثال أعلاه، السمة Speak تحتوي على دالة واحدة speak يجب أن تُنفذ من قِبل أي نوع يرغب في “تطبيق” هذه السمة.


تطبيق السمات: impl Trait

لكي يتمكن نوع (Struct أو Enum) من استخدام سمة معينة، يجب أن يُطبِّق (Implement) السمة عبر الكلمة المفتاحية impl.

rust
struct Dog; impl Speak for Dog { fn speak(&self) { println!("Woof!"); } }

بهذا الشكل، أصبح بإمكان كائن من نوع Dog استخدام الدالة speak لأن النوع قد نفذ سلوك السمة Speak.


السمات كواجهات: الفرق بين Traits وInterfaces

في اللغات الكائنية (OOP)، يُستخدم مفهوم الواجهات (Interfaces) لتحديد مجموعة من الدوال التي يجب أن تُنفذ من قِبل الأصناف (Classes). على الرغم من أن السمات في رست تشبه هذا المفهوم، إلا أنها تمتاز بما يلي:

  • عدم الاعتماد على الوراثة الصريحة: لا تحتوي رست على وراثة من الأصناف (Classes) وإنما تسمح بتكوين السلوك عبر تركيبة السمات.

  • التركيب بدل الوراثة: رست تُشجع على بناء الكائنات من خلال تركيب وظائف متعددة عن طريق السمات.

  • السلامة في وقت الترجمة: جميع تطبيقات السمات يتم التحقق من سلامتها في وقت الترجمة، مما يضمن خلو البرنامج من أخطاء وقت التنفيذ المتعلقة بالأنواع والسلوكيات.


السمات ذات التنفيذ الافتراضي

تتيح السمات في رست تضمين تنفيذ افتراضي لبعض الدوال، مما يعني أن النوع يمكنه استخدام السلوك الافتراضي دون الحاجة إلى إعادة تنفيذ الدالة.

rust
trait Greet { fn greet(&self) { println!("Hello!"); } }

ويمكن لنوع أن يستخدم هذا التنفيذ دون الحاجة إلى إعادة تعريف greet.

rust
struct Person; impl Greet for Person {}

السمات كأساليب عامة (Generic Traits)

عند العمل مع أنواع عامة، تسمح السمات بتقييد الأنواع التي يمكن استخدامها في القوالب (Generics).

rust
fn greet(item: T) { item.greet(); }

في هذا المثال، يمكن استدعاء الدالة greet فقط مع الأنواع التي تطبق السمة Greet.


السمات كأنواع: Trait Objects

عندما تحتاج إلى مرونة أكبر في التعامل مع أنواع متعددة في وقت التشغيل، توفر رست ميزة كائنات السمات (Trait Objects) باستخدام المؤشر dyn.

rust
fn greet(obj: &dyn Greet) { obj.greet(); }

هذا يسمح بتعدد الأشكال الديناميكي (Dynamic Polymorphism)، ولكنه يأتي بتكلفة أداء أكبر مقارنة بالتعدد الأشكال الثابت (Static Polymorphism).


السمات المتعددة: تعدد السمات (Trait Bounds)

يمكنك تحديد أكثر من سمة لنوع عام باستخدام عامل +.

rust
fn print_and_greet(item: T) { println!("{}", item); item.greet(); }

وراثة السمات (Trait Inheritance)

تسمح رست للسمات بأن ترث من سمات أخرى، مما يتيح تصميم سلوكيات مركبة ومعقدة بطريقة آمنة ومرنة.

rust
trait Named { fn name(&self) -> String; } trait Greet: Named { fn greet(&self) { println!("Hello, {}!", self.name()); } }

أي نوع يطبق Greet يجب أن يطبق أيضًا Named.


السمات المرتبطة (Associated Types)

تُستخدم الأنواع المرتبطة لتحديد نوع مرتبط بسمة معينة.

rust
trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }

هذا الأسلوب يوفر نوعًا من المرونة العالية ويفيد في المكتبات العامة مثل مكتبة std::iter.


استخدام السمات في البرمجة الدالة (Functional Programming)

تتكامل السمات مع نمط البرمجة الدالة في رست بشكل ممتاز. على سبيل المثال، تُستخدم السمات Fn, FnMut, وFnOnce لتعريف السلوكيات التي تقبل أو تُغلق (Capture) المتغيرات.

rust
fn apply(f: F) where F: Fn(), { f(); }

الجدول التالي يوضح أنواع السمات الشائعة في مكتبة Rust القياسية ووظائفها الأساسية:

اسم السمة الوظيفة
Clone تُمكن من نسخ الكائن إلى نسخة جديدة
Copy نسخة ضوئية من الكائن دون الحاجة إلى استدعاء clone()
Debug تسمح بطباعة النوع باستخدام {:?}
Default توفر قيمة افتراضية لنوع معين
PartialEq, Eq تُستخدم للمقارنة بين القيم
PartialOrd, Ord تُستخدم للترتيب والمقارنة الجزئية أو الكاملة
Hash تُستخدم لحساب القيم الهاشية، مفيدة في الخرائط والمجموعات
Iterator تُستخدم لتعريف أنواع قابلة للتكرار
From, Into تحويل من/إلى نوع آخر
Drop تُستخدم لتنفيذ الكود عند تحرير الموارد

السمات في سياق الأمان والملكية

السمات تتفاعل بقوة مع نظام الملكية (Ownership) في رست، ما يمنح المطور القدرة على تصميم واجهات تضمن صحة البيانات وسلوكها دون الحاجة إلى اعتماد على الوراثة التقليدية.

عند تعريف سمة تستخدم المرجع &mut self، فإن النوع الذي يطبقها يجب أن يضمن امتلاك فريد (Unique Ownership) عند التنفيذ. هذا يضيف طبقة من الأمان تساعد على تجنب ظروف التسابق والذاكرة الفاسدة.


نمط التصميم باستخدام السمات

من الاستخدامات المتقدمة للسمات هو إنشاء نمط تصميم مرن عن طريق تركيب عدة سمات لتكوين سلوك مركب ومعقد. هذا النمط يُعرف بـ التكوين فوق الوراثة (Composition over Inheritance).

rust
trait Movable { fn move_to(&mut self, x: i32, y: i32); } trait Drawable { fn draw(&self); } struct Sprite { x: i32, y: i32, } impl Movable for Sprite { fn move_to(&mut self, x: i32, y: i32) { self.x = x; self.y = y; } } impl Drawable for Sprite { fn draw(&self) { println!("Drawing at ({}, {})", self.x, self.y); } }

هذا الأسلوب يُفضَّل في تصميم الألعاب أو التطبيقات الرسومية التي تتطلب سلوكيات قابلة لإعادة الاستخدام بمرونة عالية.


السمات في البرمجة غير المتزامنة (Asynchronous Programming)

مع تطور نظام البرمجة غير المتزامنة في رست، أصبحت السمات تلعب دورًا جوهريًا في تصميم واجهات غير متزامنة باستخدام async trait.

رغم أن دعم السمات غير المتزامنة ليس جزءًا من اللغة في الوقت الحالي (حتى إصدار 1.77)، إلا أن هناك مكتبات مثل async-trait تُوفر هذه الإمكانية من خلال ماكرو يقوم بتوليد الكود المطلوب خلف الكواليس.

rust
#[async_trait::async_trait] trait AsyncGreet { async fn greet(&self); }

الختام التقني

السمات في لغة رست ليست مجرد واجهات نمطية، بل أداة تصميم فعالة تُعزز السلامة، الكفاءة، والمرونة. قدرتها على التعبير عن سلوكيات مشتركة دون التورط في مشاكل الوراثة التقليدية، مع التكامل العميق مع نظام النوع، الملكية، والتحقق في وقت الترجمة، يجعلها عنصرًا مركزيًا في تصميم برامج آمنة وسريعة.

إن إتقان استخدام السمات هو خطوة أساسية نحو احتراف لغة رست، ويمثل بوابة لفهم فلسفة التصميم الحديثة التي تتبناها اللغة في موازنة الأداء مع الأمان، والمرونة مع الانضباط النوعي.


المراجع:

  1. Rust Official Documentation: Traits

  2. The Rustonomicon: Advanced Trait System