Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

الإرسال الديناميكي (Dynamic Dispatch)

جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 45 من 61.

عندما تستخدم الأنواع العامة (generics) مع قيود السمات (trait bounds) مثل <T: Summary>، يحدد Rust النوع الدقيق في وقت التجميع (compile time). يُسمى هذا الإرسال الثابت (static dispatch)—حيث يقوم المترجم (compiler) بإنشاء كود متخصص لكل نوع ملموس (concrete type) تستخدمه. إنه سريع، ولكن هناك قيد: يمكن للمتغير أن يحمل نوعاً واحداً محدداً فقط.

ماذا لو كنت بحاجة إلى متغير واحد يمكنه الاحتفاظ بأنواع مختلفة في وقت التشغيل؟ هنا يأتي دور كائنات السمات (trait objects). باستخدام Box<dyn Trait>، يمكنك تخزين أي نوع يقوم بتنفيذ هذه السمة:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

تشير الكلمة المفتاحية dyn إلى الإرسال الديناميكي (dynamic dispatch)—حيث تبحث لغة Rust عن الطريقة التي سيتم استدعاؤها في وقت التشغيل بدلاً من وقت التجميع. يعد Box ضرورياً لأن كائنات السمات (trait objects) ليس لها حجم معروف؛ حيث يوفر Box مؤشراً بحجم ثابت.

فكر في الأمر بهذه الطريقة: تقول القوالب العامة (generics) "أنا أعمل مع النوع T"، بينما تقول كائنات السمات (trait objects) "أنا أعمل مع أي شيء يمكنه القيام بذلك". المقايضة هي تكلفة بسيطة في وقت التشغيل مقابل المرونة في التعامل مع أنواع مختلفة من خلال نفس المتغير.

challenge icon

التحدي

سهل

دعنا نبني نظاماً لتأجير المركبات يوضح قوة الإرسال الديناميكي (dynamic dispatch)! ستقوم بإنشاء سمة (trait) تحدد ما يمكن لأي مركبة قابلة للتأجير القيام به، ثم تستخدم Box<dyn Trait> لتخزين أنواع مختلفة من المركبات في نفس المتغير.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • vehicle.rs: قم بتعريف سمة عامة Rentable مع طريقة rental_info تأخذ &self وتعيد String. ثم أنشئ هيكلين (structs) عامين:
    • Car — مع حقل عام model من نوع (String). يجب أن تعيد rental_info الخاصة به "Car: {model}"
    • Bike — مع حقل عام brand من نوع (String). يجب أن تعيد rental_info الخاصة به "Bike: {brand}"
  • main.rs: قم باستيراد وحدة المركبات (vehicle module) واستخدم المدخلات لتوضيح الإرسال الديناميكي. أنشئ متغيراً من نوع Box<dyn Rentable> يحمل أولاً Car، واطبع معلومات الإيجار الخاصة به، ثم أعد تعيين نفس المتغير ليحمل Bike واطبع معلومات الإيجار الخاصة به مرة أخرى.

السحر هنا هو أن متغيراً واحداً من نوع Box<dyn Rentable> يمكنه حمل إما Car أو Bike—حيث يحدد Rust أي طريقة rental_info سيتم استدعاؤها في وقت التشغيل (runtime). هذا هو الإرسال الديناميكي في أبهى صوره!

يجب أن يظهر مخرجك معلومات كلتا المركبتين:

Car: {model}
Bike: {brand}

على سبيل المثال، مع المدخلات "Tesla Model 3" و "Trek":

Car: Tesla Model 3
Bike: Trek

ستتلقى مدخلين: طراز السيارة (car model) وعلامة الدراجة التجارية (bike brand).

ورقة مرجعية

تستخدم لغة Rust **الإرسال الساكن (static dispatch)** مع القوالب العامة (generics)—حيث يقوم المترجم بإنشاء كود مخصص لكل نوع في وقت الترجمة. هذا سريع ولكنه يعني أن المتغير يمكنه حمل نوع واحد محدد فقط.

تمكن **كائنات السمات (Trait objects)** من **الإرسال الديناميكي (dynamic dispatch)**، مما يسمح لمتغير واحد بحمل أنواع مختلفة في وقت التشغيل. استخدم Box<dyn Trait> لتخزين أي نوع يطبق هذه السمة:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

تشير الكلمة المفتاحية dyn إلى الإرسال الديناميكي—حيث يتم حل استدعاءات الدوال في وقت التشغيل. يعد Box مطلوباً لأن كائنات السمات ليس لها حجم معروف في وقت الترجمة.

الفرق الجوهري: تقول القوالب العامة (Generics) "أنا أعمل مع النوع T" (نوع واحد محدد)، بينما تقول كائنات السمات "أنا أعمل مع أي شيء يطبق هذه السمة" (أنواع متعددة من خلال نفس المتغير). المقايضة هي تكلفة بسيطة في وقت التشغيل مقابل المرونة.

جرّب بنفسك

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // قراءة المدخلات
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: يحمل سيارة Box<dyn Rentable> قم بإنشاء متغير من نوع
    // rental_info() اطبع معلومات الإيجار الخاصة به باستخدام الدالة
    
    // TODO: أعد تعيين نفس المتغير ليحمل دراجة
    // اطبع معلومات الإيجار الخاصة به مرة أخرى
    
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming