السمات والأنواع العامة في Rust: تجريد بلا كلفة أداء
السمات: تعريف سلوك مشترك
السمات (Traits) تُعرّف سلوكاً مشتركاً يمكن لأنواع مختلفة تطبيقه. فكّر فيها كعقد: أي نوع يُطبّق السمة يضمن وجود هذه الدوال.
#[derive(Debug)]
enum SensorError {
Timeout,
Disconnected,
InvalidReading(f64),
}
// سمة تُعرّف سلوك "القراءة" لأي مستشعر
trait Readable {
fn read(&self) -> Result<f64, SensorError>;
fn unit(&self) -> &str;
// دالة افتراضية — يمكن تجاوزها
fn display_reading(&self) -> String {
match self.read() {
Ok(val) => format!("{:.2} {}", val, self.unit()),
Err(e) => format!("خطأ: {:?}", e),
}
}
}
تطبيق السمات لأنواع مختلفة
كل نوع مستشعر يُطبّق السمة بطريقته الخاصة، لكن الواجهة واحدة.
struct TemperatureSensor {
id: u32,
current_temp: f64,
}
struct PressureSensor {
id: u32,
current_pressure: f64,
max_rating: f64,
}
struct VibrationSensor {
id: u32,
amplitude: f64,
}
impl Readable for TemperatureSensor {
fn read(&self) -> Result<f64, SensorError> {
if self.current_temp > 500.0 {
Err(SensorError::InvalidReading(self.current_temp))
} else {
Ok(self.current_temp)
}
}
fn unit(&self) -> &str { "°C" }
}
impl Readable for PressureSensor {
fn read(&self) -> Result<f64, SensorError> {
if self.current_pressure > self.max_rating {
Err(SensorError::InvalidReading(self.current_pressure))
} else {
Ok(self.current_pressure)
}
}
fn unit(&self) -> &str { "bar" }
}
impl Readable for VibrationSensor {
fn read(&self) -> Result<f64, SensorError> {
Ok(self.amplitude)
}
fn unit(&self) -> &str { "mm/s" }
// تجاوز الدالة الافتراضية
fn display_reading(&self) -> String {
match self.read() {
Ok(val) if val > 8.0 => format!("⚠ اهتزاز عالٍ: {:.2} {}", val, self.unit()),
Ok(val) => format!("{:.2} {}", val, self.unit()),
Err(e) => format!("خطأ: {:?}", e),
}
}
}
السمات المُشتقة: Debug و Clone و PartialEq
Rust يمكنها إنشاء تطبيقات تلقائية لسمات شائعة باستخدام #[derive].
// derive يُنشئ التطبيق تلقائياً
#[derive(Debug, Clone, PartialEq)]
struct SensorConfig {
id: u32,
name: String,
min_threshold: f64,
max_threshold: f64,
poll_interval_ms: u64,
}
fn main() {
let config = SensorConfig {
id: 101,
name: String::from("حرارة المحرك"),
min_threshold: 10.0,
max_threshold: 95.0,
poll_interval_ms: 1000,
};
// Debug: طباعة للتشخيص
println!("الإعدادات: {:?}", config);
// Clone: نسخة مستقلة
let mut backup = config.clone();
backup.poll_interval_ms = 500;
// PartialEq: مقارنة
if config != backup {
println!("الإعدادات تغيرت — يجب إعادة التهيئة");
}
}
الأنواع العامة: كتابة دوال لأي نوع
الأنواع العامة (Generics) تتيح كتابة دالة واحدة تعمل مع أنواع متعددة. حدود السمات تضمن أن النوع يملك السلوك المطلوب.
// هذه الدالة تعمل مع أي نوع يُطبّق Readable
fn log_reading<T: Readable>(sensor: &T) {
match sensor.read() {
Ok(value) => {
println!("[سجل] القراءة: {:.2} {}", value, sensor.unit());
}
Err(e) => {
println!("[إنذار] فشل القراءة: {:?}", e);
}
}
}
// دالة عامة مع عدة حدود سمات
fn compare_readings<T: Readable, U: Readable>(
sensor_a: &T,
sensor_b: &U,
) -> Option<f64> {
let a = sensor_a.read().ok()?;
let b = sensor_b.read().ok()?;
Some((a - b).abs())
}
fn main() {
let temp = TemperatureSensor { id: 1, current_temp: 75.0 };
let pressure = PressureSensor { id: 2, current_pressure: 3.2, max_rating: 10.0 };
// نفس الدالة تعمل مع أنواع مختلفة
log_reading(&temp);
log_reading(&pressure);
}
حدود السمات وعبارات where
عند تعدد الحدود، عبارة where تجعل التوقيع أوضح وأسهل للقراءة.
use std::fmt;
// بدون where — يصبح التوقيع طويلاً ومزدحماً
fn process_v1<T: Readable + fmt::Display + Clone>(sensor: &T) {
println!("{}", sensor);
}
// مع where — أنظف وأوضح
fn process_and_log<T, L>(sensor: &T, logger: &L)
where
T: Readable + fmt::Display,
L: fmt::Write,
{
let reading = sensor.display_reading();
println!("معالجة: {}", reading);
}
// حدود متعددة في هيكل عام
struct MonitoringStation<T>
where
T: Readable + Clone,
{
sensors: Vec<T>,
name: String,
}
impl<T> MonitoringStation<T>
where
T: Readable + Clone,
{
fn new(name: &str) -> Self {
MonitoringStation {
sensors: Vec::new(),
name: String::from(name),
}
}
fn add_sensor(&mut self, sensor: T) {
self.sensors.push(sensor);
}
fn read_all(&self) -> Vec<Result<f64, SensorError>> {
self.sensors.iter().map(|s| s.read()).collect()
}
}
impl Trait في توقيعات الدوال
impl Trait صيغة مختصرة لتحديد نوع يُطبّق سمة معينة.
مفيدة خاصة في أنواع الإعادة حيث لا يعرف المُستدعي النوع الدقيق.
// في المعاملات: بديل مختصر للأنواع العامة
fn check_sensor(sensor: &impl Readable) -> bool {
sensor.read().is_ok()
}
// في نوع الإعادة: النوع الدقيق مخفي
fn create_temperature_sensor(id: u32) -> impl Readable {
TemperatureSensor {
id,
current_temp: 25.0,
}
}
// مفيدة عند إعادة أنواع معقدة مثل المُكررات
fn active_readings(sensors: &[Box<dyn Readable>]) -> Vec<f64> {
sensors.iter()
.filter_map(|s| s.read().ok())
.collect()
}
fn main() {
// لا نحتاج لمعرفة النوع الدقيق
let sensor = create_temperature_sensor(301);
println!("القراءة: {}", sensor.display_reading());
}
مثال عملي: مُسجّل مستشعرات عام
نجمع كل ما تعلمناه لبناء مُسجّل يعمل مع أي نوع مستشعر.
use std::fmt;
trait Readable: fmt::Debug {
fn read(&self) -> Result<f64, SensorError>;
fn unit(&self) -> &str;
fn sensor_id(&self) -> u32;
}
// مُسجّل عام يعمل مع أي مستشعر يُطبّق Readable
struct SensorLogger {
entries: Vec<LogEntry>,
max_entries: usize,
}
struct LogEntry {
sensor_id: u32,
value: f64,
unit: String,
timestamp: String,
}
impl SensorLogger {
fn new(max_entries: usize) -> Self {
SensorLogger {
entries: Vec::new(),
max_entries,
}
}
// تسجيل قراءة من أي مستشعر
fn log<T: Readable>(&mut self, sensor: &T, timestamp: &str) {
match sensor.read() {
Ok(value) => {
if self.entries.len() >= self.max_entries {
self.entries.remove(0); // حذف الأقدم
}
self.entries.push(LogEntry {
sensor_id: sensor.sensor_id(),
value,
unit: String::from(sensor.unit()),
timestamp: String::from(timestamp),
});
}
Err(e) => {
println!("[خطأ] المستشعر {}: {:?}",
sensor.sensor_id(), e);
}
}
}
// تقرير القراءات لمستشعر معين
fn report_for(&self, sensor_id: u32) {
println!("--- تقرير المستشعر {} ---", sensor_id);
for entry in self.entries.iter()
.filter(|e| e.sensor_id == sensor_id)
{
println!(" [{}] {:.2} {}",
entry.timestamp, entry.value, entry.unit);
}
}
// متوسط القراءات لمستشعر
fn average_for(&self, sensor_id: u32) -> Option<f64> {
let readings: Vec<f64> = self.entries.iter()
.filter(|e| e.sensor_id == sensor_id)
.map(|e| e.value)
.collect();
if readings.is_empty() {
None
} else {
Some(readings.iter().sum::<f64>() / readings.len() as f64)
}
}
}
fn main() {
let mut logger = SensorLogger::new(1000);
let temp = TemperatureSensor { id: 101, current_temp: 82.5 };
let pressure = PressureSensor {
id: 201, current_pressure: 4.8, max_rating: 10.0,
};
let vibration = VibrationSensor { id: 301, amplitude: 3.2 };
// مُسجّل واحد لجميع أنواع المستشعرات
logger.log(&temp, "2026-04-14 08:00");
logger.log(&pressure, "2026-04-14 08:00");
logger.log(&vibration, "2026-04-14 08:00");
logger.report_for(101);
if let Some(avg) = logger.average_for(101) {
println!("متوسط الحرارة: {:.1}°C", avg);
}
}
الخلاصة
- السمات تُعرّف سلوكاً مشتركاً يمكن لأنواع مختلفة تطبيقه
- كل نوع يُطبّق السمة بطريقته الخاصة مع واجهة موحدة
- derive يُنشئ تطبيقات تلقائية لسمات شائعة مثل Debug و Clone
- الأنواع العامة تكتب دوالاً وهياكل تعمل مع أي نوع يحقق الشروط
- حدود السمات تضمن أن النوع العام يملك السلوك المطلوب
- عبارة where تُنظّم الحدود المتعددة بوضوح
- impl Trait صيغة مختصرة في المعاملات وأنواع الإعادة
- الجمع بين السمات والأنواع العامة يُنتج كوداً مرناً وآمناً وقابلاً لإعادة الاستخدام