الرئيسية قاعدة المعرفة البرمجة والمنطق الملكية في Rust: النظام الذي يمنع أعطال الذاكرة نهائياً
البرمجة والمنطق

الملكية في Rust: النظام الذي يمنع أعطال الذاكرة نهائياً

القواعد الثلاث للملكية

نظام الملكية هو القلب النابض لـ Rust — السبب الذي يجعلها آمنة بدون جامع قمامة. القواعد بسيطة لكن آثارها عميقة:

  1. كل قيمة في Rust لها مالك واحد فقط (متغير واحد)
  2. لا يمكن أن يكون لقيمة أكثر من مالك في نفس الوقت
  3. عندما يخرج المالك من النطاق، تُحرّر القيمة تلقائياً
fn main() {
    {
        let readings = vec![23.5, 24.1, 22.8]; // readings تملك هذا Vec
        println!("عدد القراءات: {}", readings.len());
    } // readings خرجت من النطاق → الذاكرة تُحرّر تلقائياً

    // لا يمكن استخدام readings هنا — لم تعد موجودة
}

في C++ كنت ستحتاج delete أو free يدوياً — أو تعتمد على جامع قمامة يوقف البرنامج أحياناً. Rust تفعل هذا تلقائياً بدون أي كلفة وقت التشغيل.

المكدس مقابل الكومة: أين تعيش البيانات؟

لفهم الملكية، تحتاج معرفة أين تُخزّن البيانات:

المكدس (Stack) الكومة (Heap)
سريع جداً أبطأ (يحتاج تخصيص)
حجم ثابت معروف وقت التجميع حجم متغير وقت التشغيل
i32, f64, bool, char String, Vec<T>, HashMap
نسخ تلقائي (Copy) نقل ملكية (Move)
// على المكدس — نسخ بسيط
let a: i32 = 42;
let b = a;       // b نسخة مستقلة — a لا تزال صالحة
println!("{} {}", a, b); // يعمل!

// على الكومة — نقل ملكية
let s1 = String::from("مستشعر_حرارة");
let s2 = s1;     // الملكية انتقلت إلى s2 — s1 لم تعد صالحة
// println!("{}", s1); // خطأ تجميع! s1 نُقلت
println!("{}", s2); // يعمل

دلالات النقل: نقل الملكية

عندما تُسند قيمة على الكومة لمتغير آخر أو تمررها لدالة، تنتقل الملكية — المتغير الأصلي يصبح غير صالح:

fn process_data(data: Vec<f64>) {
    println!("معالجة {} قراءة", data.len());
    // data تُحرّر هنا عند خروجها من النطاق
}

fn main() {
    let sensor_data = vec![23.5, 24.1, 22.8, 25.0];
    process_data(sensor_data);  // الملكية انتقلت إلى الدالة

    // sensor_data لم تعد صالحة — المُجمِّع يمنع استخدامها
    // println!("{:?}", sensor_data); // خطأ تجميع!
}

لماذا؟ لأن Rust تضمن أن مسؤول واحد فقط يحرر الذاكرة — لا تحرير مزدوج (double free)، لا مؤشرات معلّقة.

تدفق البيانات في خط إنتاج

fn read_sensors() -> Vec<f64> {
    vec![23.5, 24.1, 22.8] // تنشئ وتُعيد الملكية للمستدعي
}

fn filter_alarms(data: Vec<f64>) -> Vec<f64> {
    data.into_iter().filter(|&t| t > 24.0).collect()
}

fn log_alarms(alarms: Vec<f64>) {
    for temp in &alarms {
        println!("إنذار: {:.1}°C", temp);
    }
}

fn main() {
    let data = read_sensors();      // main تملك data
    let alarms = filter_alarms(data); // الملكية انتقلت — data لم تعد صالحة
    log_alarms(alarms);             // الملكية انتقلت مرة أخرى
}

البيانات تتدفق من قارئ → مُعالج → مُسجّل، كل خطوة تملك البيانات مؤقتاً ثم تنقلها.

النسخ والاستنساخ: متى تُكرَّر القيم؟

Copy: نسخ تلقائي للأنواع البسيطة

الأنواع الصغيرة الموجودة على المكدس تُنسخ تلقائياً:

let sensor_id: u32 = 42;
let backup_id = sensor_id; // نسخة — كلاهما صالح
println!("أصلي: {}, نسخة: {}", sensor_id, backup_id);

الأنواع التي تدعم Copy: i32, f64, bool, char, (i32, f64) (tuples من أنواع Copy).

Clone: نسخ صريح للأنواع المعقدة

لنسخ قيمة على الكومة، استخدم .clone() — نسخ عميق صريح:

let original = String::from("خط إنتاج A");
let copy = original.clone(); // نسخ عميق — كلاهما صالح

println!("أصلي: {}", original);
println!("نسخة: {}", copy);

تنبيه: .clone() ينسخ كل البيانات — إذا كان لديك Vec بمليون عنصر، سيُنشئ نسخة كاملة. استخدمه بحذر في الأنظمة الصناعية حيث الأداء مهم.

الملكية في التطبيق: خط معالجة بيانات المستشعرات

لنجمع كل ما تعلمناه في مثال واقعي:

struct SensorReading {
    id: u32,          // Copy — على المكدس
    timestamp: u64,   // Copy
    value: f64,       // Copy
    unit: String,     // Move — على الكومة
}

fn create_reading(id: u32, value: f64, unit: &str) -> SensorReading {
    SensorReading {
        id,
        timestamp: 1700000000,
        value,
        unit: String::from(unit),
    }
}

fn format_reading(reading: SensorReading) -> String {
    // reading انتقلت إلى هنا — المستدعي لن يستخدمها بعد
    format!("[{}] المستشعر {}: {:.1} {}",
        reading.timestamp, reading.id, reading.value, reading.unit)
}

fn main() {
    let r = create_reading(101, 23.5, "°C");
    let formatted = format_reading(r);
    // r لم تعد صالحة — انتقلت إلى format_reading
    println!("{}", formatted);
}

هذا التدفق واضح: كل دالة تملك بياناتها، وعند انتهائها تُحرّر تلقائياً. لا تسريبات، لا أخطاء.

الخلاصة

نظام الملكية يضمن أمان الذاكرة بثلاث قواعد بسيطة: مالك واحد، وقت واحد، تحرير تلقائي. الأنواع البسيطة (أعداد، منطقيات) تُنسخ تلقائياً عبر Copy. الأنواع المعقدة (String, Vec) تُنقل — ولنسخها تحتاج .clone(). هذا النظام يمنع تسريبات الذاكرة والمؤشرات المعلّقة بدون أي كلفة وقت التشغيل. لكن ماذا لو أردت مشاركة البيانات بدون نقلها؟ هذا موضوع الدرس القادم — الاستعارة والمراجع.

ownership move copy clone stack heap الملكية النقل النسخ المكدس الكومة أمان الذاكرة