الوحدات والحزم في 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 يُدير التبعيات والميزات الاختيارية بسهولة
- مساحات العمل تُوحّد المشاريع الكبيرة متعددة الحزم تحت سقف واحد
- في الدرس القادم سنتعلم التزامن وتشغيل عدة مهام بالتوازي