الرئيسية قاعدة المعرفة البرمجة والمنطق الوحدات والحزم في Rust: تنظيم مشروع صناعي كبير
البرمجة والمنطق

الوحدات والحزم في Rust: تنظيم مشروع صناعي كبير

نظام الوحدات: mod والرؤية

في المصانع الحقيقية، كل قسم له صلاحيات محددة. نظام الوحدات في Rust يعمل بنفس المبدأ: كل شيء خاص (private) افتراضياً، ونستخدم pub لفتح الوصول.

// تعريف وحدة المستشعرات
mod sensors {
    // دالة عامة - يمكن استدعاؤها من خارج الوحدة
    pub fn read_temperature() -> f64 {
        let raw = read_raw_adc(); // استدعاء دالة خاصة داخلياً
        calibrate(raw)
    }

    // دالة خاصة - لا يمكن الوصول إليها من خارج الوحدة
    fn read_raw_adc() -> u16 {
        4023 // قراءة المحوّل التماثلي
    }

    fn calibrate(raw: u16) -> f64 {
        raw as f64 * 0.025
    }

    // وحدة فرعية
    pub mod pressure {
        pub fn read() -> f64 {
            1.013 // ضغط جوي
        }
    }
}

fn main() {
    let temp = sensors::read_temperature();       // يعمل
    let press = sensors::pressure::read();        // يعمل
    // let raw = sensors::read_raw_adc();         // خطأ! دالة خاصة
    println!("الحرارة: {temp}° | الضغط: {press} بار");
}

الوحدات القائمة على الملفات

عندما يكبر المشروع، ننقل كل وحدة إلى ملف مستقل. Rust يدعم أسلوبين:

مشروع_المصنع/
├── src/
│   ├── main.rs          // نقطة الدخول
│   ├── sensors.rs       // الأسلوب الأول: ملف واحد
│   └── alarms/          // الأسلوب الثاني: مجلد
│       ├── mod.rs       // تعريف الوحدة الرئيسية
│       └── rules.rs     // وحدة فرعية
// src/main.rs
mod sensors;  // يبحث عن sensors.rs أو sensors/mod.rs
mod alarms;   // يبحث عن alarms/mod.rs

fn main() {
    let temp = sensors::read_temperature();
    alarms::check(temp);
}
// src/sensors.rs
pub fn read_temperature() -> f64 {
    72.5
}
// src/alarms/mod.rs
mod rules;  // يُحمّل alarms/rules.rs

pub fn check(temperature: f64) {
    if rules::is_critical(temperature) {
        println!("إنذار حرج!");
    }
}
// src/alarms/rules.rs
pub fn is_critical(temp: f64) -> bool {
    temp > 90.0
}

use والمسارات: استيراد العناصر

بدلاً من كتابة المسار الكامل كل مرة، نستخدم use للاستيراد:

// استيراد دالة محددة
use crate::sensors::temperature::read;

// استيراد الوحدة نفسها
use crate::sensors::temperature;

// استيراد عدة عناصر
use crate::alarms::{check_temperature, check_pressure, AlarmLevel};

// استيراد مع إعادة تسمية لتجنب التضارب
use crate::sensors::temperature::Reading as TempReading;
use crate::sensors::pressure::Reading as PressReading;

// استيراد من المكتبة القياسية
use std::collections::HashMap;

fn main() {
    let t = read();                    // بدل crate::sensors::temperature::read()
    let r = TempReading { value: t };
    let mut map = HashMap::new();
    map.insert("T-01", r);
}

الحزم: المكتبات والملفات التنفيذية

الحزمة (crate) هي وحدة التجميع في Rust. هناك نوعان:

مشروع_المصنع/
├── Cargo.toml
├── src/
│   ├── lib.rs      // حزمة مكتبة - كود قابل لإعادة الاستخدام
│   └── main.rs     // حزمة تنفيذية - نقطة الدخول
// src/lib.rs - المكتبة: تصدّر الوظائف المشتركة
pub mod sensors;
pub mod alarms;
pub mod reporting;

/// حساب متوسط قراءات خط الإنتاج
pub fn average(readings: &[f64]) -> f64 {
    let sum: f64 = readings.iter().sum();
    sum / readings.len() as f64
}
// src/main.rs - التنفيذي: يستخدم المكتبة
use factory_monitor::{sensors, alarms, average};

fn main() {
    let readings = sensors::collect_batch();
    let avg = average(&readings);
    alarms::check_all(avg);
    println!("متوسط الدفعة: {avg:.2}");
}

Cargo.toml: المكتبات الخارجية والميزات

ملف Cargo.toml يُدير التبعيات والإعدادات:

[package]
name = "factory-monitor"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }      # تشغيل غير متزامن
serde = { version = "1", features = ["derive"] }     # تسلسل البيانات
serde_json = "1"                                      # صيغة JSON
chrono = "0.4"                                        # التعامل مع الوقت

[dev-dependencies]
# تبعيات للاختبارات فقط
assert_approx_eq = "1.1"

[features]
# ميزات اختيارية يختارها المستخدم
default = ["logging"]
logging = []                   # تفعيل سجلات التشغيل
remote-monitoring = ["tokio"]  # مراقبة عن بعد تحتاج tokio
// استخدام مكتبة خارجية في الكود
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

#[derive(Serialize, Deserialize)]
struct MachineEvent {
    machine_id: u32,
    event_type: String,
    timestamp: DateTime<Utc>,
    value: f64,
}

مساحات العمل: مشاريع متعددة الحزم

للمشاريع الكبيرة، نستخدم مساحة عمل (workspace) تضم عدة حزم:

dr-machine/
├── Cargo.toml              // ملف مساحة العمل الرئيسي
├── crates/
│   ├── dm-sensors/         // حزمة المستشعرات
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   ├── dm-alarms/          // حزمة الإنذارات
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   └── dm-server/          // الخادم التنفيذي
│       ├── Cargo.toml
│       └── src/main.rs
# Cargo.toml الرئيسي
[workspace]
members = [
    "crates/dm-sensors",
    "crates/dm-alarms",
    "crates/dm-server",
]

# تبعيات مشتركة بين جميع الحزم
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# crates/dm-alarms/Cargo.toml
[package]
name = "dm-alarms"
version = "0.1.0"
edition = "2021"

[dependencies]
dm-sensors = { path = "../dm-sensors" }  # يعتمد على حزمة محلية
serde.workspace = true                    # يستخدم النسخة المشتركة

مثال عملي: هيكلة نظام مراقبة مصنع

factory-monitor/
├── Cargo.toml
├── crates/
│   ├── sensors/          // قراءة المستشعرات
│   │   └── src/lib.rs
│   ├── processing/       // معالجة البيانات
│   │   └── src/lib.rs
│   ├── alarms/           // نظام الإنذارات
│   │   └── src/lib.rs
│   └── server/           // واجهة الويب
│       └── src/main.rs
// crates/sensors/src/lib.rs
pub struct Reading {
    pub sensor_id: String,
    pub value: f64,
    pub unit: String,
}

pub fn collect_all() -> Vec<Reading> {
    vec![
        Reading { sensor_id: "T-01".into(), value: 72.5, unit: "°C".into() },
        Reading { sensor_id: "P-03".into(), value: 1.2, unit: "bar".into() },
    ]
}

// crates/processing/src/lib.rs
use sensors::Reading;

pub fn compute_average(readings: &[Reading]) -> f64 {
    let sum: f64 = readings.iter().map(|r| r.value).sum();
    sum / readings.len() as f64
}

// crates/alarms/src/lib.rs
pub enum AlarmLevel { Normal, Warning, Critical }

pub fn evaluate(sensor_id: &str, value: f64) -> AlarmLevel {
    match (sensor_id.starts_with("T"), value) {
        (true, v) if v > 90.0 => AlarmLevel::Critical,
        (true, v) if v > 80.0 => AlarmLevel::Warning,
        _ => AlarmLevel::Normal,
    }
}

// crates/server/src/main.rs
use sensors::collect_all;
use processing::compute_average;
use alarms::{evaluate, AlarmLevel};

fn main() {
    let readings = collect_all();
    let avg = compute_average(&readings);
    for r in &readings {
        match evaluate(&r.sensor_id, r.value) {
            AlarmLevel::Critical => println!("[حرج] {} = {}", r.sensor_id, r.value),
            AlarmLevel::Warning  => println!("[تحذير] {} = {}", r.sensor_id, r.value),
            AlarmLevel::Normal   => println!("[طبيعي] {} = {}", r.sensor_id, r.value),
        }
    }
    println!("المتوسط العام: {avg:.2}");
}

الخلاصة

  • كل شيء في Rust خاص افتراضياً؛ استخدم pub لفتح الوصول حسب الحاجة
  • الوحدات القائمة على الملفات تُنظّم المشاريع الكبيرة بوضوح
  • use يُبسّط الاستيراد ويمنع تكرار المسارات الطويلة
  • الحزمة تكون مكتبة (lib.rs) أو تنفيذية (main.rs) أو كلاهما
  • Cargo.toml يُدير التبعيات والميزات الاختيارية بسهولة
  • مساحات العمل تُوحّد المشاريع الكبيرة متعددة الحزم تحت سقف واحد
  • في الدرس القادم سنتعلم التزامن وتشغيل عدة مهام بالتوازي
modules crates Cargo pub use workspace الوحدات الحزم التنظيم الرؤية المكتبات مساحة العمل