Rust: سمات Rust: تعريف السلوك المشترك

آخر تحديث: 2026-08-26

السمة (trait) هي ما يعادل «الواجهة» في لغة Rust — فهي تُعرِّف مجموعة من توقيعات الطرق، ويمكن لأنواع مختلفة أن تُنفِّذ نفس السمة، وبذلك تتشارك في نفس العقد السلوكي.

إذا كان نوع الجهاز هو «ماهية هذا الشيء»، فإن السمة هي «ما يمكن لهذا الشيء أن يفعله». خذ منفذ USB-C على سبيل المثال — سواء كان جهاز هاتف أو كمبيوتر محمول أو جهاز لوحي، فما دام يدعم USB-C، يمكنك توصيله بالشحن. قد تختلف الآليات الداخلية لهذه الأجهزة اختلافًا كبيرًا، لكن السلوك الذي تعد به المستخدمين يظل ثابتًا.


1. ما ستتعلمه



2. المخططات المفاهيمية

يوضح الرسم التخطيطي التالي الذي يعتمد على نموذج «حورية البحر» سلسلة العلاقات الكاملة لصفة ما — بدءًا من تعريفها مرورًا بتطبيقها على أنواع مختلفة، وصولاً إلى استدعائها المتعدد الأشكال:

100%
graph LR
    A["trait UsbCCharge"] -->|Defining a Behavioral Contract| B["fn charge(&self)"]
    A --> C["fn voltage(&self) -> u32"]
    A --> D["fn charge_time(&self) -> String"]
    B --> E["impl for Phone<br/>charge: 18W"]
    B --> F["impl for Laptop<br/>charge: 65W"]
    B --> G["impl for Tablet<br/>charge: 30W"]
    E --> H["phone.charge()"]
    F --> I["laptop.charge()"]
    G --> J["tablet.charge()"]
    H --> K["Polymorphic Call<br/>The Same trait Interface<br/>Different types of behavior vary"]
    I --> K
    J --> K


3. قصة الشاحن متعدد الاستخدامات

(1) المشكلة: لكل جهاز طريقة شحن خاصة به

ليو (Leo) هو مهندس معماري يعمل في شركة إلكترونيات. تتوسع خطوط إنتاج الشركة لتشمل الهواتف وأجهزة الكمبيوتر المحمولة والأجهزة اللوحية، ولكل جهاز طريقة شحن خاصة به.

في البداية، كان يكتب كود الشحن بشكل منفصل لكل جهاز:

RUST
struct Phone;
struct Laptop;
struct Tablet;

impl Phone {
    fn charge(&self) {
        println!("Phone: charging via USB-C at 18W");
    }
}

impl Laptop {
    fn charge(&self) {
        println!("Laptop: charging via USB-C at 65W");
    }
}

impl Tablet {
    fn charge(&self) {
        println!("Tablet: charging via USB-C at 30W");
    }
}

fn main() {
    let phone = Phone;
    let laptop = Laptop;
    let tablet = Tablet;

    phone.charge();
    laptop.charge();
    tablet.charge();
}

تحتوي كل بنية من البنى الثلاث على طريقة charge — فهي تشترك في الاسم والتوقيع نفسه، لكن لا يوجد «توافق» بينها. إذا أردت كتابة دالة «محطة شحن عالمية» لشحن أي جهاز — فلن يكون ذلك ممكنًا. فكل نوع من الأجهزة مستقل بذاته، ولا توجد طبقة مجردة مشتركة بينها.

(2) متطلبات أكثر تعقيدًا: الشحن الدفعي

طلب مدير المنتج تطوير «محطة شحن عالمية» قادرة على شحن عدة أنواع مختلفة من الأجهزة في آن واحد:

RUST
// This code cannot be compiled.——Unknown parameter type
// fn charge_all_devices(devices: ???) {
//     for device in devices {
//         device.charge();
//     }
// }

بدون هذه السمة، لا يمكن لـ charge_all_devices قبول المجموعات ذات الأنواع المختلطة. إما أن تقوم بإعادة تحميل دالة لكل نوع، أو تتخلى عن ذلك.

(3) نهج السمات في لغة Rust

RUST
// Define a trait: a shared behavior contract
trait UsbCCharge {
    fn charge(&self);
    fn voltage(&self) -> u32 { 18 }  // Default method with default voltage
}

struct Phone;
struct Laptop;
struct Tablet;

impl UsbCCharge for Phone {
    fn charge(&self) {
        println!("Phone: charging via USB-C at {}W", self.voltage());
    }
    // voltage() uses the default (18W)
}

impl UsbCCharge for Laptop {
    fn charge(&self) {
        println!("Laptop: charging via USB-C at {}W", self.voltage());
    }
    fn voltage(&self) -> u32 { 65 }  // Override default
}

impl UsbCCharge for Tablet {
    fn charge(&self) {
        println!("Tablet: charging via USB-C at {}W", self.voltage());
    }
    fn voltage(&self) -> u32 { 30 }  // Override default
}

// Generic function: accepts any type that implements UsbCCharge
fn charge_device<T: UsbCCharge>(device: &T) {
    device.charge();
}

fn main() {
    charge_device(&Phone);
    charge_device(&Laptop);
    charge_device(&Tablet);
}

الناتج:

TEXT 📖 للعرض فقط
Phone: charging via USB-C at 18W
Laptop: charging via USB-C at 65W
Tablet: charging via USB-C at 30W

يُعرِّف trait UsbCCharge «عقد سلوك الشحن». ويمكن لـ charge_device قبول أي نوع يُنفِّذ هذه السمة. ويحتوي voltage() على تنفيذ افتراضي (18 واط)، لكن «الكمبيوتر المحمول» و«الجهاز اللوحي» يختاران تجاوز هذا التنفيذ. وهذه هي القيمة الأساسية للسمة — تحديد السلوك المشترك مع السماح بتنفيذات متباينة.



4. المفاهيم الأساسية

(1) نظرة عامة على نظام السمات

100%
graph TB
    A[Rust Trait System] --> B[Trait Definition]
    A --> C[Implementing Traits]
    A --> D[Derivable Traits]
    A --> E[Trait as Parameters]
    A --> F[Trait Objects]
    A --> G[Trait Inheritance]

    B --> B1["trait Name { fn method(&self); }"]
    C --> C1["impl TraitName for MyType { ... }"]
    C --> C2["Default methods in trait"]

    D --> D1["#[derive(Debug, Clone, Copy, PartialEq)]"]
    D --> D2["Compiler auto-generates implementation"]

    E --> E1["fn foo(x: impl Trait)"]
    E --> E2["fn foo<T: Trait>(x: T)"]
    E --> E3["fn foo<T>(x: T) where T: Trait"]

    F --> F1["Box<dyn Trait>"]
    F --> F2["Runtime dispatch (vtable)"]

    G --> G1["trait A: SuperTrait { }"]
    G --> G2["Inherits methods from SuperTrait"]

(2) التوزيع الثابت مقابل التوزيع الديناميكي

ميزة القيود العامة T: Trait / impl Trait مثيلات السمات dyn Trait
توقيت التوزيع وقت التجميع (التوزيع الثابت) وقت التشغيل (التوزيع الديناميكي)
التنفيذ نمط «سينجلتون» — إنشاء كود منفصل لكل نوع الجدول الافتراضي (vtable) — استدعاء غير مباشر عبر المؤشرات
الأداء بدون عبء إضافي (يمكن تضمينها في السطر نفسه) عبء الاستدعاء غير المباشر
حجم الملف الثنائي أكبر (نسخة واحدة لكل نوع) أصغر (نسخة واحدة من الكود)
متطلبات النوع يتم تحديد النوع المحدد عند وقت الاستدعاء قد تختلف الأنواع، شريطة أن تنفذ نفس السمة
حالات الاستخدام حساسة للأداء، مع أنواع معروفة في وقت التحويل البرمجي تتطلب مجموعات غير متجانسة، مع أنواع تُحدد في وقت التشغيل

(3) السمات القابلة للاشتقاق الشائعة الاستخدام

السمة الوظيفة السلوك الذي يتم إنشاؤه تلقائيًا
Debug إخراج منسق {:?} إنشاء إخراج تصحيح الأخطاء؛ طباعة أسماء حقول البنية وقيمها
Clone نسخ .clone() بشكل صريح إنشاء طريقة clone، مع النسخ حقلًا حقلًا
Copy النسخ الضمني (النسخ بتًا بتًا) لا يتم نقل الملكية أثناء عملية التعيين؛ بل يتم نسخ القيمة بدلاً من ذلك
PartialEq مقارنة المساواة == / != إنشاء طريقة eq، والمقارنة حقلًا بحقل
Eq التكافؤ التام (علاقة التكافؤ الرياضية) استنادًا إلى PartialEq، مع ضمان إضافي للانعكاسية
Hash حساب التجزئة طريقة لتوليد hash، بحساب قيم التجزئة حقلًا حقلًا
Default القيمة الافتراضية إنشاء الأسلوب default، باستخدام القيمة الافتراضية لكل حقل

(4) مرجع سريع لدمج قيود السمات

نوع القيد الصيغة حالات الاستخدام مثال
قيد فردي مضمن fn foo<T: Trait>(x: T) قيد فردي بسيط fn charge<T: UsbCCharge>(d: &T)
قيود متعددة مضمنة fn foo<T: Trait1 + Trait2>(x: T) قيود متعددة fn describe<T: Debug + UsbCCharge>(d: &T)
جملة WHERE fn foo<T>(x: T) where T: Trait القيود المعقدة، المعلمات متعددة الأنواع where T: UsbCCharge, U: Debug
سمة impl fn foo(x: impl Trait) تبسيط صيغة البرمجة fn plug(d: &impl USBDevice)
السمة impl + fn foo(x: impl Trait1 + Trait2) بسيطة ومقيدة fn describe(d: &(impl USBDevice + Debug))


5. أمثلة على السكتة الدماغية

(1) ▶ المثال:تعريفات السمات والطرق الافتراضية — شاحن الجهاز (مستوى الصعوبة ⭐)

RUST
// ============================================
// Trait definition with default methods
// ============================================

// Define a trait: any device that can charge via USB-C
trait UsbCCharge {
    // Required method: must be implemented
    fn charge(&self);

    // Default method: implementor MAY override
    fn voltage(&self) -> u32 {
        18  // Default: standard USB-C 18W
    }

    // Another default method using self.voltage()
    fn charge_time(&self, battery_capacity_mah: u32) -> String {
        let hours = battery_capacity_mah as f64 / (self.voltage() as f64 * 1000.0 / 5.0);
        format!("{:.1} hours to full charge", hours)
    }
}

struct Phone;
struct Laptop;
struct Tablet;

impl UsbCCharge for Phone {
    fn charge(&self) {
        // Uses the default voltage() -> 18W
        println!("Phone: charging at {}W (standard speed)", self.voltage());
    }
    // voltage() uses default, charge_time() uses default
}

impl UsbCCharge for Laptop {
    fn charge(&self) {
        println!("Laptop: charging at {}W (fast charging)", self.voltage());
    }

    fn voltage(&self) -> u32 {
        65  // Laptop needs more power
    }
}

impl UsbCCharge for Tablet {
    fn charge(&self) {
        println!("Tablet: charging at {}W (medium speed)", self.voltage());
    }

    fn voltage(&self) -> u32 {
        30
    }
}

fn main() {
    let phone = Phone;
    let laptop = Laptop;
    let tablet = Tablet;

    phone.charge();
    println!("  -> {}", phone.charge_time(3000));

    laptop.charge();
    println!("  -> {}", laptop.charge_time(6000));

    tablet.charge();
    println!("  -> {}", tablet.charge_time(5000));
}

الناتج:

TEXT 📖 للعرض فقط
Phone: charging at 18W (standard speed)
  -> 0.8 hours to full charge
Laptop: charging at 65W (fast charging)
  -> 0.5 hours to full charge
Tablet: charging at 30W (medium speed)
  -> 0.8 hours to full charge

في السمة UsbCCharge، تُعد charge() طريقة إلزامية — يجب على كل مُنفِّذ توفيرها. voltage() وcharge_time() هما طريقتان افتراضيتان — فهما توفران تنفيذًا افتراضيًا، ويمكن للمطورين اختيار تجاوزهما أو استخدامهما كما هما. يقوم Phone بتنفيذ charge() فقط ويستخدم التنفيذات الافتراضية للطريقتين الأخريين؛ بينما يقوم Laptop بتجاوز voltage() ويستخدم التنفيذات الافتراضية للباقي.


(2) ▶ المثال:اشتقاق السمات — تصحيح الأخطاء، والاستنساخ، والمقارنة (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Derivable traits: Debug, Clone, Copy, PartialEq
// ============================================

// Without deriving, none of these operations would work
#[derive(Debug, Clone, Copy, PartialEq)]
struct ChargerSpec {
    brand: &'static str,
    watts: u32,
    usb_c: bool,
}

// PartialEq is needed for this custom type
#[derive(Debug, Clone, PartialEq)]
struct Device {
    name: String,
    required_watts: u32,
}

fn main() {
    // --- Debug: pretty-print with {:?} ---
    let spec1 = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
    println!("Debug: {:?}", spec1);
    println!("Pretty: {:#?}", spec1);

    // --- Clone: explicit copy ---
    let spec2 = spec1.clone();   // spec1 is still valid
    println!("Cloned: {:?}", spec2);

    // --- Copy: implicit copy (only if Copy is derived) ---
    let spec3 = spec1;            // spec1 is STILL valid because ChargerSpec is Copy!
    println!("Copied (implicit): {:?}", spec3);
    println!("Original still valid: {:?}", spec1);  // Works!

    // --- PartialEq: equality comparison ---
    let spec_a = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
    let spec_b = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
    let spec_c = ChargerSpec { brand: "Baseus", watts: 65, usb_c: true };

    println!("spec_a == spec_b: {}", spec_a == spec_b);  // true
    println!("spec_a == spec_c: {}", spec_a == spec_c);  // false (brand differs)
    println!("spec_a != spec_c: {}", spec_a != spec_c);  // true

    // --- Practical: filtering devices ---
    let phone = Device {
        name: String::from("Phone"),
        required_watts: 18,
    };
    let laptop = Device {
        name: String::from("Laptop"),
        required_watts: 65,
    };

    // Clone a device
    let phone_backup = phone.clone();
    println!("\nPhone backup: {:?}", phone_backup);

    // Compare devices by required_watts
    let charger_watts = 65;
    let compatible = vec![phone, laptop]
        .iter()
        .filter(|d| d.required_watts <= charger_watts)
        .collect::<Vec<_>>();
    println!("Compatible devices (<= {}W): {:?}", charger_watts, compatible);
}

الناتج:

TEXT 📖 للعرض فقط
Debug: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Pretty: ChargerSpec {
    brand: "Anker",
    watts: 65,
    usb_c: true,
}
Cloned: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Copied (implicit): ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Original still valid: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
spec_a == spec_b: true
spec_a == spec_c: false
spec_a != spec_c: true

Phone backup: Device { name: "Phone", required_watts: 18 }
Compatible devices (<= 65W): [Device { name: "Phone", required_watts: 18 }, Device { name: "Laptop", required_watts: 65 }]

باستخدام سطر واحد فقط من التعليمات البرمجية #[derive(Debug, Clone, Copy, PartialEq)]، قام مُركِّب لغة Rust تلقائيًّا بإنشاء تطبيقات لأربع سمات ChargerSpec. لاحظ الفرق بين Copy وClone: تتطلب Clone استدعاءً صريحًا لـ .clone()، في حين أن Copy هي نسخة بتية ضمنية (لا تنطوي عملية التعيين على نقل). لا يحتوي Device على Copy لأن النوع String لا ينفذ Copy (فهو يخصص الذاكرة في الكومة).


(3) ▶ المثال:السمات كمعلمات — السمات الضمنية والقيود العامة (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Trait as parameter: impl Trait, generic bounds, where clause
// ============================================

trait USBDevice {
    fn device_name(&self) -> &str;
    fn power_draw(&self) -> u32;
}

struct Mouse;
struct Keyboard;
struct Webcam;

impl USBDevice for Mouse {
    fn device_name(&self) -> &str { "Mouse" }
    fn power_draw(&self) -> u32 { 2 }
}

impl USBDevice for Keyboard {
    fn device_name(&self) -> &str { "Keyboard" }
    fn power_draw(&self) -> u32 { 3 }
}

impl USBDevice for Webcam {
    fn device_name(&self) -> &str { "Webcam" }
    fn power_draw(&self) -> u32 { 5 }
}

// --- Style 1: impl Trait (sugar for simple cases) ---
fn plug_device(device: &impl USBDevice) {
    println!("[Plugged] {} (draws {}W)", device.device_name(), device.power_draw());
}

// --- Style 2: Generic bound T: Trait (explicit type parameter) ---
fn print_device_spec<T: USBDevice>(device: &T) {
    println!("[Spec] {} - Power: {}W", device.device_name(), device.power_draw());
}

// --- Style 3: where clause (best for complex bounds) ---
fn check_compatible<T>(device: &T, max_power: u32) -> bool
where
    T: USBDevice,
{
    device.power_draw() <= max_power
}

// --- Style 4: Multiple trait bounds ---
use std::fmt::Debug;
fn describe_device(device: &(impl USBDevice + Debug)) {
    println!("[Debug] Device: {:?}", device);
}

fn main() {
    let mouse = Mouse;
    let keyboard = Keyboard;
    let webcam = Webcam;

    // impl Trait syntax
    plug_device(&mouse);
    plug_device(&keyboard);

    // Generic bound syntax
    print_device_spec(&webcam);

    // where clause
    println!("\n--- Compatibility Check (max 3W) ---");
    println!("Mouse compatible: {}", check_compatible(&mouse, 3));
    println!("Keyboard compatible: {}", check_compatible(&keyboard, 3));
    println!("Webcam compatible: {}", check_compatible(&webcam, 3));

    // Calculate total power draw for a list
    let devices: Vec<&dyn USBDevice> = vec![&mouse, &keyboard, &webcam];
    let total_power: u32 = devices.iter().map(|d| d.power_draw()).sum();
    println!("\nTotal power draw: {}W / 15W budget", total_power);
}

الناتج:

TEXT 📖 للعرض فقط
[Plugged] Mouse (draws 2W)
[Plugged] Keyboard (draws 3W)
[Spec] Webcam - Power: 5W

--- Compatibility Check (max 3W) ---
Mouse compatible: true
Keyboard compatible: true
Webcam compatible: false

Total power draw: 10W / 15W budget

لكل نمط من أنماط معلمات السمات الأربعة حالة استخدام خاصة به: impl Trait (موجز، ومناسب لسمة واحدة)، T: Trait (أسماء معلمات النوع صريحة، ومناسب لأنواع المراجع)، where T: Trait (يوفر أفضل قابلية للقراءة عند وجود قيود متعددة)، impl Trait + AnotherTrait (قيود متعددة. لاحظ أن Vec<&dyn USBDevice> هو كائن سمة (انظر المثال التالي) — يُستخدم هنا لتخزين المراجع من أنواع مختلفة.)


(4) ▶ المثال:كائن السمة dyn Trait ووراثة السمات (مستوى الصعوبة: ⭐⭐⭐)

RUST
// ============================================
// dyn Trait (runtime dispatch) + Trait inheritance
// ============================================

use std::fmt::Debug;

// --- Super trait (trait inheritance) ---
// AnyDevice "inherits" from Debug: to implement AnyDevice,
// a type must also implement Debug
trait AnyDevice: Debug {
    fn model_name(&self) -> &str;
}

// UsbDevice extends AnyDevice: it requires Debug + AnyDevice
trait UsbDevice: AnyDevice {
    fn usb_version(&self) -> &str;
    fn transfer_speed(&self) -> &str;
}

// --- Implement the trait hierarchy ---
#[derive(Debug)]
struct FlashDrive {
    name: String,
    capacity_gb: u32,
}

impl AnyDevice for FlashDrive {
    fn model_name(&self) -> &str {
        &self.name
    }
}

impl UsbDevice for FlashDrive {
    fn usb_version(&self) -> &str {
        "USB 3.2 Gen 2"
    }

    fn transfer_speed(&self) -> &str {
        "10 Gbps"
    }
}

#[derive(Debug)]
struct ExternalSSD {
    name: String,
    capacity_tb: f64,
}

impl AnyDevice for ExternalSSD {
    fn model_name(&self) -> &str {
        &self.name
    }
}

impl UsbDevice for ExternalSSD {
    fn usb_version(&self) -> &str {
        "USB 3.2 Gen 2x2"
    }

    fn transfer_speed(&self) -> &str {
        "20 Gbps"
    }
}

// --- Function using trait objects ---
// Accept a heterogeneous collection of UsbDevice implementors
fn list_devices(devices: &[Box<dyn UsbDevice>]) {
    for (i, device) in devices.iter().enumerate() {
        println!(
            "Device #{}: {} ({} - {}, Debug: {:?})",
            i + 1,
            device.model_name(),
            device.usb_version(),
            device.transfer_speed(),
            device,
        );
    }
}

// --- Function returning a trait object ---
fn make_device(device_type: &str) -> Option<Box<dyn UsbDevice>> {
    match device_type {
        "flash" => Some(Box::new(FlashDrive {
            name: String::from("SanDisk 128GB"),
            capacity_gb: 128,
        })),
        "ssd" => Some(Box::new(ExternalSSD {
            name: String::from("Samsung T7 1TB"),
            capacity_tb: 1.0,
        })),
        _ => None,
    }
}

fn main() {
    // Heterogeneous collection: different types, same trait
    let drive1 = Box::new(FlashDrive {
        name: String::from("Kingston 64GB"),
        capacity_gb: 64,
    });
    let drive2 = Box::new(ExternalSSD {
        name: String::from("WD My Passport 2TB"),
        capacity_tb: 2.0,
    });

    let all_devices: Vec<Box<dyn UsbDevice>> = vec![drive1, drive2];
    println!("--- Connected Devices ---");
    list_devices(&all_devices);

    // Factory function returning trait objects
    println!("\n--- Device Factory ---");
    if let Some(device) = make_device("ssd") {
        println!("Created: {} ({} - {})", device.model_name(), device.usb_version(), device.transfer_speed());
    }

    // Trait inheritance in action: UsbDevice requires Debug
    // so we can use both {:?} and trait methods
    println!("\n--- Debug via Super Trait ---");
    let flash = FlashDrive {
        name: String::from("Lexar 32GB"),
        capacity_gb: 32,
    };
    // flash has Debug (from AnyDevice: Debug), AnyDevice, and UsbDevice
    println!("Debug: {:?}", flash);
    println!("Model: {}", flash.model_name());
    println!("USB: {}", flash.usb_version());
}

الناتج:

TEXT 📖 للعرض فقط
--- Connected Devices ---
Device #1: Kingston 64GB (USB 3.2 Gen 2 - 10 Gbps, Debug: FlashDrive { name: "Kingston 64GB", capacity_gb: 64 })
Device #2: WD My Passport 2TB (USB 3.2 Gen 2x2 - 20 Gbps, Debug: ExternalSSD { name: "WD My Passport 2TB", capacity_tb: 2.0 })

--- Device Factory ---
Created: Samsung T7 1TB (USB 3.2 Gen 2x2 - 20 Gbps)

--- Debug via Super Trait ---
Debug: FlashDrive { name: "Lexar 32GB", capacity_gb: 32 }
Model: Lexar 32GB
USB: USB 3.2 Gen 2

وراثة السمات (السمة الفائقة): trait AnyDevice: Debug تعني أن «أي نوع يُنفِّذ AnyDevice يجب أن يُنفِّذ أيضًا Debug». يُضاف trait UsbDevice: AnyDevice فوق ذلك — مكونًا تسلسلًا هرميًا للسمات من ثلاث مستويات. يجب على الأنواع التي تنفذ UsbDevice أن تنفذ أيضًا جميع طرق Debug + AnyDevice + UsbDevice.

كائن السمة dyn Trait: باستخدام Box<dyn UsbDevice>، يمكنك تخزين كائنات من أنواع مختلفة تنفذ نفس السمة في مجموعة واحدة. يتم توزيع استدعاءات الطرق في وقت التشغيل عبر الجدول الافتراضي (vtable) — وهذا يؤدي إلى زيادة طفيفة في الأداء ولكنه يوفر مرونة هائلة. تجسد الدالة make_device، التي تُرجع Option<Box<dyn UsbDevice>>، «نمط المصنع».


(5) ▶ المثال:تمرين شامل — حساب مساحة الأشكال (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Trait + Generic Constraints + dyn Trait
// ============================================

use std::fmt::Debug;

trait Shape: Debug {
    fn area(&self) -> f64;
    fn name(&self) -> &str;
    fn describe(&self) -> String {
        format!("{}: Area = {:.2}", self.name(), self.area())
    }
}

#[derive(Debug)]
struct Circle { radius: f64 }
#[derive(Debug)]
struct Rectangle { width: f64, height: f64 }
#[derive(Debug)]
struct Triangle { base: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
    fn name(&self) -> &str { "Circular" }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
    fn name(&self) -> &str { "Rectangle" }
}

impl Shape for Triangle {
    fn area(&self) -> f64 { 0.5 * self.base * self.height }
    fn name(&self) -> &str { "Triangle" }
}

fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
    shapes.iter().map(|s| s.area()).sum()
}

fn largest<T: Shape>(shapes: &[T]) -> &T {
    shapes.iter().max_by(|a, b| a.area().partial_cmp(&b.area()).unwrap()).unwrap()
}

fn print_all(shapes: &[Box<dyn Shape>]) {
    for s in shapes {
        println!("  {}", s.describe());
    }
}

fn main() {
    let shapes_static: Vec<&dyn Shape> = vec![
        &Circle { radius: 5.0 },
        &Rectangle { width: 4.0, height: 6.0 },
        &Triangle { base: 3.0, height: 8.0 },
    ];

    println!("=== Static Reference Traversal ===");
    for s in &shapes_static {
        println!("  {}", s.describe());
    }

    let shapes_dynamic: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 10.0 }),
        Box::new(Rectangle { width: 3.0, height: 7.0 }),
        Box::new(Triangle { base: 6.0, height: 4.0 }),
    ];

    println!("\n=== Dynamic Distribution ===");
    print_all(&shapes_dynamic);
    println!("Total Area: {:.2}", total_area(&shapes_dynamic));

    let homogenous = vec![
        Circle { radius: 3.0 },
        Circle { radius: 7.0 },
        Circle { radius: 5.0 },
    ];
    let biggest = largest(&homogenous);
    println!("\nLargest Circle: {}", biggest.describe());
}

الناتج:

TEXT 📖 للعرض فقط
=== Static Reference Traversal ===
  Circular: Area = 78.54
  Rectangle: Area = 24.00
  Triangle: Area = 12.00

=== Dynamic Distribution ===
  Circular: Area = 314.16
  Rectangle: Area = 21.00
  Triangle: Area = 12.00
Total Area: 347.16

Largest Circle: Circular: Area = 153.94

تُستخدم السمة نفسها Shape بثلاث طرق: &dyn Shape للإشارات الثابتة إلى الشرائح، وBox<dyn Shape> لتوزيع المجموعات الديناميكية، وT: Shape لتحديد القيمة القصوى كقيد عام. describe هي الطريقة الافتراضية للسمة، والتي يرثها جميع المُنفِّذون تلقائيًّا.



❓ أسئلة شائعة

س ما الفرق بين impl Trait وdyn Trait؟ ومتى ينبغي استخدام كل منهما؟
ج يستخدم impl Trait التوزيع الثابت في وقت التحويل البرمجي، بينما يستخدم dyn Trait التوزيع الديناميكي في وقت التشغيل.
س ما الفرق بين #[derive(Debug)] والتنفيذ اليدوي؟
ج يقوم derive تلقائيًا بإنشاء كود نموذجي، وهو مناسب للبنيات البسيطة التي تحتوي على عدد قليل من الحقول.
س ما الفرق بالضبط بين Copy وClone؟
ج Copy هي نسخة بتية ضمنية (لا تنقل عملية التعيين الملكية)، بينما Clone هي نسخة عميقة صريحة (تستدعي .clone()).
س ما الفرق بين وراثة السمات (trait) ووراثة الفئات (class) في اللغات الموجهة للكائنات؟
ج وراثة السمات في Rust هي «وراثة الواجهة» (وراثة توقيعات الطرق)، وليست «وراثة التنفيذ» (وراثة التنفيذات + الحالة).
س هل الرموز T: Trait1 + Trait2 وwhere T: Trait1 + Trait2 في قيود السمات متطابقة؟
ج لهما نفس المعنى تمامًا؛ لكنهما يختلفان في أسلوب الصياغة فقط.
س ما الغرض من السمة الفارغة (السمة المُعلِّمة)؟
ج لا تحتوي السمة المُعلِّمة على أي طرق؛ وهي تُستخدم لـ«تسمية» نوع ما بهدف تمكين سلوكيات أو قيود معينة في المُترجم.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: عرّف trait Drawable { fn draw(&self); }. قم بتنفيذ هذه السمة في البنيتين Circle وSquare، بحيث تعرض كل منهما شكلاً مختلفًا. اكتب دالة عامة fn render<T: Drawable>(item: &T) تستدعي draw. في الدالة الرئيسية، قم بعرض دائرة ومربع على التوالي.

  2. الصعوبة ⭐⭐: عرّف سمة trait Summary { fn summarize(&self) -> String; fn author(&self) -> &str; }، حيث author() هي الطريقة الافتراضية التي تُرجع "Anonymous". قم بتنفيذ هذه السمة على البنيتين Article { title: String, content: String } وTweet { username: String, text: String }. أنشئ مقالتين وتغريدتين، وضعهما في Vec<Box<dyn Summary>>، وقم بالتكرار عبرهما لطباعة ملخصاتهما.

  3. الصعوبة ⭐⭐⭐: حدد تسلسلاً هرميًا للسمات: trait Vehicle: std::fmt::Debug { fn fuel_type(&self) -> &str; }، ثم trait ElectricVehicle: Vehicle { fn battery_capacity_kwh(&self) -> f64; fn range_km(&self) -> f64; }. قم بتنفيذ ElectricVehicle للبنيات TeslaModel3 وNissanLeaf. اكتب دالة fn print_fleet(vehicles: &[Box<dyn ElectricVehicle>]) للتكرار عبر كل سيارة وطباعة المعلومات الخاصة بها. في الدالة الرئيسية، أنشئ مثيلات لكلا النوعين من السيارات واختبرهما.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%