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

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

جزء من قسم البرمجة كائنية التوجه في رحلة Rust على Coddy. الدرس 45 من 61.

عند استخدام الأنواع العامة مع قيود السمات مثل <T: Summary>، تحدد Rust النوع الدقيق في وقت الترجمة. يُسمى هذا الإرسال الساكن: ينشئ المترجم تعليمات برمجية متخصصة لكل نوع ملموس تستخدمه. إنه سريع، لكن هناك قيدًا: لا يمكن للمتغير أن يحتفظ إلا بنوع محدد واحد.

ماذا لو احتجت إلى متغير واحد يمكنه الاحتفاظ بأنواع مختلفة أثناء التشغيل؟ هنا يأتي دور كائنات السمات. باستخدام 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 إلى التوزيع الديناميكي: تبحث Rust عن الطريقة التي يجب استدعاؤها في وقت التشغيل بدلًا من وقت الترجمة. تُعد Box ضرورية لأن كائنات السمة ليس لها حجم معروف؛ إذ توفر Box مؤشرًا ذا حجم ثابت.

فكّر في الأمر بهذه الطريقة: تقول الأنواع العامة «أعمل مع النوع T»، بينما تقول كائنات السمات «أعمل مع أي شيء يمكنه فعل ذلك». والمقابل هو تكلفة صغيرة أثناء التشغيل مقابل المرونة في التعامل مع أنواع مختلفة من خلال المتغير نفسه.

challenge icon

التحدي

سهل

لنَبْنِ نظامًا لتأجير المركبات يوضّح قوة الإرسال الديناميكي! ستنشئ سمةً تحدد ما يمكن لأي مركبة قابلة للتأجير القيام به، ثم تستخدم Box<dyn Trait> لتخزين أنواع مختلفة من المركبات في المتغير نفسه.

ستنظّم شفرتك عبر ملفين:

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

تكمن الفكرة الرائعة هنا في أن متغيرًا واحدًا من النوع Box<dyn Rentable> يمكنه الاحتفاظ إما بكائن Car أو بكائن Bike: إذ تحدد Rust أي طريقة rental_info يجب استدعاؤها أثناء التشغيل. هذا هو الإرسال الديناميكي في الواقع!

يجب أن يعرض ناتجك معلومات كلتا المركبتين:

Car: {model}
Bike: {brand}

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

Car: Tesla Model 3
Bike: Trek

ستتلقى مدخلين: طراز السيارة والعلامة التجارية للدراجة.

جرّب بنفسك

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> يحمل Car
    // اطبع معلومات الإيجار الخاصة به باستخدام الدالة rental_info()
    
    // TODO: أعد تعيين نفس المتغير ليحمل Bike
    // اطبع معلومات الإيجار الخاصة به مرة أخرى
    
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Rust عبر الإنترنت