Rust: سمات Rust: تعريف السلوك المشترك
آخر تحديث: 2026-08-26
السمة (trait) هي ما يعادل «الواجهة» في لغة Rust — فهي تُعرِّف مجموعة من توقيعات الطرق، ويمكن لأنواع مختلفة أن تُنفِّذ نفس السمة، وبذلك تتشارك في نفس العقد السلوكي.
إذا كان نوع الجهاز هو «ماهية هذا الشيء»، فإن السمة هي «ما يمكن لهذا الشيء أن يفعله». خذ منفذ USB-C على سبيل المثال — سواء كان جهاز هاتف أو كمبيوتر محمول أو جهاز لوحي، فما دام يدعم USB-C، يمكنك توصيله بالشحن. قد تختلف الآليات الداخلية لهذه الأجهزة اختلافًا كبيرًا، لكن السلوك الذي تعد به المستخدمين يظل ثابتًا.
1. ما ستتعلمه
- قواعد بناء الجملة لتعريف سمة
trait Name { fn method(&self); }وتنفيذ سمة لنوع ما - الطريقة الافتراضية — توفير تنفيذ افتراضي في تعريف السمة؛ ويمكن لمن يقومون بالتنفيذ اختيار تجاوزها
- السمة القابلة للاشتقاق:
#[derive(Debug, Clone, Copy, PartialEq)] - السمات كمعلمات:
impl Traitقواعد الصياغة والقيود العامةT: Trait - كائن السمة
dyn Trait— التوزيع الديناميكي أثناء وقت التشغيل - وراثة السمات (السمة الفائقة): ترث إحدى السمات الطرق من سمة أخرى
2. المخططات المفاهيمية
يوضح الرسم التخطيطي التالي الذي يعتمد على نموذج «حورية البحر» سلسلة العلاقات الكاملة لصفة ما — بدءًا من تعريفها مرورًا بتطبيقها على أنواع مختلفة، وصولاً إلى استدعائها المتعدد الأشكال:
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) هو مهندس معماري يعمل في شركة إلكترونيات. تتوسع خطوط إنتاج الشركة لتشمل الهواتف وأجهزة الكمبيوتر المحمولة والأجهزة اللوحية، ولكل جهاز طريقة شحن خاصة به.
في البداية، كان يكتب كود الشحن بشكل منفصل لكل جهاز:
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) متطلبات أكثر تعقيدًا: الشحن الدفعي
طلب مدير المنتج تطوير «محطة شحن عالمية» قادرة على شحن عدة أنواع مختلفة من الأجهزة في آن واحد:
// This code cannot be compiled.——Unknown parameter type
// fn charge_all_devices(devices: ???) {
// for device in devices {
// device.charge();
// }
// }
بدون هذه السمة، لا يمكن لـ charge_all_devices قبول المجموعات ذات الأنواع المختلطة. إما أن تقوم بإعادة تحميل دالة لكل نوع، أو تتخلى عن ذلك.
(3) نهج السمات في لغة 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);
}
الناتج:
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) نظرة عامة على نظام السمات
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) ▶ المثال:تعريفات السمات والطرق الافتراضية — شاحن الجهاز (مستوى الصعوبة ⭐)
// ============================================
// 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));
}
الناتج:
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) ▶ المثال:اشتقاق السمات — تصحيح الأخطاء، والاستنساخ، والمقارنة (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
الناتج:
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) ▶ المثال:السمات كمعلمات — السمات الضمنية والقيود العامة (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
الناتج:
[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 ووراثة السمات (مستوى الصعوبة: ⭐⭐⭐)
// ============================================
// 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());
}
الناتج:
--- 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) ▶ المثال:تمرين شامل — حساب مساحة الأشكال (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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());
}
الناتج:
=== 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()).T: Trait1 + Trait2 وwhere T: Trait1 + Trait2 في قيود السمات متطابقة؟📖 ملخص
- تعريف السمة
trait Name { fn method(&self); }يُعلن عن مجموعة من العقود السلوكية؛ بينما يوفر كتلة impl تطبيقات ملموسة لهذه العقود لأنواع محددة. - الطرق الافتراضية توفر تطبيقات افتراضية في السمة؛ ويمكن لمن يقومون بالتنفيذ اختيار تجاوزها أو استخدامها كما هي.
- السمة المشتقة
#[derive(Debug, Clone, Copy, PartialEq)]تتيح للمُجمِّع إنشاء تطبيقات السمة الشائعة تلقائيًا — تجريد دون أي تكلفة impl Trait(التوزيع الثابت) وdyn Trait(التوزيع الديناميكي) هما طريقتان لاستخدام السمات — فالأول لا ينطوي على أي عبء إضافي، بينما يوفر الثاني مرونة أكبر- وراثة السمات
trait A: SuperTraitإنشاء تسلسل هرمي للسمات — يجب على المُنفِّذين استيفاء جميع قيود السمات الفائقة الخاصة بهم في آن واحد - تُعد السمات الآلية الأساسية في لغة Rust لتنفيذ التجريد بدون تكلفة — فهي بمثابة «واجهات» (عناصر عامة) دون العبء الإضافي الذي تفرضه الدوال الافتراضية، كما أنها تدعم تعدد الأشكال الاختياري في وقت التشغيل عند الحاجة (كائنات السمات).
📝 تمارين
-
الصعوبة ⭐: عرّف
trait Drawable { fn draw(&self); }. قم بتنفيذ هذه السمة في البنيتينCircleوSquare، بحيث تعرض كل منهما شكلاً مختلفًا. اكتب دالة عامةfn render<T: Drawable>(item: &T)تستدعيdraw. في الدالة الرئيسية، قم بعرض دائرة ومربع على التوالي. -
الصعوبة ⭐⭐: عرّف سمة
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>>، وقم بالتكرار عبرهما لطباعة ملخصاتهما. -
الصعوبة ⭐⭐⭐: حدد تسلسلاً هرميًا للسمات:
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>])للتكرار عبر كل سيارة وطباعة المعلومات الخاصة بها. في الدالة الرئيسية، أنشئ مثيلات لكلا النوعين من السيارات واختبرهما.