الدوال في Rust: بناء وحدات برمجية قابلة لإعادة الاستخدام
تعريف الدوال واستدعاؤها
الدوال هي اللبنة الأساسية لتنظيم الكود. في Rust، تُعرّف بكلمة fn مع تحديد أنواع المعاملات والقيمة المرجعة بشكل صريح:
fn read_temperature(sensor_id: u8) -> f64 {
// محاكاة قراءة مستشعر
let raw_value = sensor_id as f64 * 10.0 + 22.5;
raw_value
}
fn main() {
let temp = read_temperature(3);
println!("درجة الحرارة: {:.1}°C", temp);
}
القواعد الأساسية:
- اسم الدالة بأحرف صغيرة مع شرطات سفلية:
read_temperatureوليسreadTemperature - كل معامل يجب أن يحمل نوعاً صريحاً:
sensor_id: u8 - نوع الإرجاع يُحدد بعد
->:-> f64 - إذا لم تُرجع شيئاً، يُحذف
->(تُرجع()ضمنياً)
المعاملات والقيم المرجعة
عدة معاملات
fn calibrate_reading(raw: f64, offset: f64, scale: f64) -> f64 {
(raw + offset) * scale
}
fn main() {
let calibrated = calibrate_reading(1024.0, -50.0, 0.1);
println!("القراءة المُعايرة: {:.2}", calibrated); // 97.40
}
دوال بدون قيمة مرجعة
fn log_alarm(machine_id: u32, message: &str) {
println!("[إنذار] الآلة {}: {}", machine_id, message);
}
عدة قيم مرجعة باستخدام Tuple
fn sensor_stats(readings: &[f64]) -> (f64, f64) {
let min = readings.iter().cloned().fold(f64::INFINITY, f64::min);
let max = readings.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
(min, max)
}
fn main() {
let data = [23.5, 24.1, 22.8, 25.0, 23.9];
let (min, max) = sensor_stats(&data);
println!("الحد الأدنى: {:.1}°C، الحد الأقصى: {:.1}°C", min, max);
}
التعبيرات كقيم مرجعة
في Rust، آخر تعبير في الدالة بدون فاصلة منقوطة هو القيمة المرجعة تلقائياً:
fn is_overheating(temp: f64) -> bool {
temp > 80.0 // لا فاصلة منقوطة = هذه القيمة المرجعة
}
// مكافئ لـ:
fn is_overheating_explicit(temp: f64) -> bool {
return temp > 80.0; // return صريح مع فاصلة منقوطة
}
القاعدة: تجنب return إلا للخروج المبكر من منتصف الدالة:
fn validate_reading(value: f64) -> Result<f64, String> {
if value < -40.0 {
return Err("القراءة أقل من الحد الأدنى للمستشعر".to_string());
}
if value > 150.0 {
return Err("القراءة أعلى من الحد الأقصى للمستشعر".to_string());
}
Ok(value) // القيمة المرجعة الطبيعية — بدون return
}
الإغلاقات: دوال مجهولة الاسم
الإغلاقات (Closures) هي دوال مختصرة يمكنها التقاط متغيرات من محيطها:
fn main() {
let calibration_factor = 0.1;
let offset = -50.0;
// إغلاق يلتقط calibration_factor و offset من المحيط
let calibrate = |raw: f64| -> f64 {
(raw + offset) * calibration_factor
};
let readings = vec![1024.0, 1050.0, 980.0, 1100.0];
for raw in &readings {
println!("خام: {} → مُعاير: {:.2}", raw, calibrate(*raw));
}
}
الإغلاقات مفيدة جداً مع المكررات:
let readings = vec![23.5, 85.2, 24.1, 90.0, 22.8];
// تصفية القراءات المرتفعة فقط
let alarms: Vec<&f64> = readings
.iter()
.filter(|&&temp| temp > 80.0)
.collect();
println!("إنذارات: {:?}", alarms); // [85.2, 90.0]
مقدمة في الوحدات: تنظيم الكود
عندما يكبر المشروع، تحتاج تقسيم الكود. Rust تستخدم الوحدات (modules) للتنظيم:
// تعريف وحدة داخل نفس الملف
mod sensors {
pub fn read_temperature(id: u8) -> f64 {
id as f64 * 10.0 + 22.5
}
pub fn read_pressure(id: u8) -> f64 {
id as f64 * 5.0 + 1013.0
}
// دالة خاصة — لا يمكن استدعاؤها من خارج الوحدة
fn raw_to_calibrated(raw: u16) -> f64 {
raw as f64 / 10.0
}
}
mod alarms {
pub fn check_temperature(temp: f64) -> bool {
temp > 80.0
}
}
fn main() {
let temp = sensors::read_temperature(3);
if alarms::check_temperature(temp) {
println!("إنذار حرارة!");
}
}
pub: يجعل الدالة مرئية من خارج الوحدة- بدون
pub: الدالة خاصة (الافتراضي) ::للوصول لعناصر الوحدة
سنتعمق في الوحدات والحزم في درس لاحق. الفكرة الأساسية: قسّم كل مسؤولية (مستشعرات، إنذارات، سجلات) في وحدة منفصلة.
الخلاصة
الدوال في Rust تتطلب أنواعاً صريحة للمعاملات والقيم المرجعة — هذا يكشف الأخطاء وقت التجميع. آخر تعبير بدون فاصلة منقوطة هو القيمة المرجعة. الإغلاقات تلتقط المتغيرات من محيطها وتُستخدم بكثرة مع المكررات. الوحدات تنظّم الكود في أقسام واضحة. في الدرس القادم سنتعمق في أهم مفهوم في Rust — نظام الملكية.