تخطَّ إلى المحتوى

🦀 شرح Rust

الكائنات السمة والإرسال الديناميكي

الدرس 25 من 29· ⏱ 2 دقائق قراءة

المشكلة: أنواع مختلفة، سلوك واحد

Generics (الدرس السابق) تعمل مع نوع واحد محدّد وقت الترجمة. لكن ماذا لو أردت قائمة تحوي عدّة أنواع مختلفة، طالما كلها تنفّذ نفس السمة (trait)؟

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14 * self.radius * self.radius }
}

impl Shape for Square {
    fn area(&self) -> f64 { self.side * self.side }
}

Vec<T> من الدرس السابق لا يقبل خلط Circle وSquare معًا — النوع T واحد لكل القائمة.

الحل: Box<dyn Trait>

dyn Trait هو كائن سمة (trait object): مؤشّر لقيمة تنفّذ السمة، بحجم غير معروف وقت الترجمة، لذا يجب تغليفه بمؤشّر مثل Box:

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Square { side: 3.0 }),
    ];

    for s in &shapes {
        println!("{:.2}", s.area());   // يستدعي التنفيذ الصحيح لكل عنصر
    }
}

قائمة واحدة، أنواع مختلفة، كلها تشترك في سمة Shape.

الإرسال الساكن مقابل الديناميكي

الجانبGenerics (T: Shape)dyn Shape
القراروقت الترجمة (static dispatch)وقت التشغيل (dynamic dispatch)
الأداءأسرع — لا بحث وقت التشغيلأبطأ قليلًا — جدول مؤشّرات (vtable)
المرونةنوع واحد لكل استدعاءأنواع متعدّدة بنفس المجموعة
حجم الكودأكبر (نسخة لكل نوع)أصغر (كود مشترك)

&dyn Trait كمعامل دالة

لا يلزم Box دائمًا؛ مرجع بسيط يكفي إن لم تحتَج ملكية:

fn print_area(shape: &dyn Shape) {
    println!("المساحة: {:.2}", shape.area());
}

print_area(&Circle { radius: 1.0 });

💡 ابدأ بـ Generics دائمًا (أسرع وأوضح للمترجم)، وانتقل لـ dyn Trait فقط عندما تحتاج فعليًا خلط أنواع مختلفة في نفس البنية وقت التشغيل.

🎯 التالي: مطابقة الأنماط المتقدّمة.

شرح الكائنات السمة والإرسال الديناميكي — Rust بالعربي
الكائنات السمة والإرسال الديناميكيRust بالعربي · The Code Fix

📚 لمزيد من التعمّق في Rust، راجِع كتاب Rust الرسمي.

هل كان هذا الدرس مفيدًا؟