البرمجة الوظيفية في Rust: من المكررات إلى الأحادية (Monads)
Rust هي لغة متعددة النماذج تمزج بسلاسة بين الأساليب الأمرية (imperative) والموجهة للكائنات والوظيفية. على الرغم من أن Rust ليست لغة وظيفية بحتة (مثل Haskell)، إلا أن نظام الأنواع والملكية الخاص بها يسمح بكتابة كود أنيق وآمن ومعبّر بأسلوب وظيفي. في هذه المقالة، سنشرح المفاهيم الأساسية للبرمجة الوظيفية (FP) في Rust: الإغلاقات (closures)، المكررات (iterators)، مطابقة الأنماط (pattern matching)، بالإضافة إلى العمل مع Option و Result. ستكون المادة مفيدة لكل من المبتدئين والمطورين ذوي الخبرة الذين يرغبون في فهم أعمق للإمكانيات الوظيفية لـ Rust.
الإغلاقات (closures) وتعبيرات لامدا (lambda expressions)
الإغلاقات في Rust هي دوال مجهولة يمكنها التقاط المتغيرات من النطاق المحيط بها. إنها أساس النهج الوظيفي، مما يسمح بتمرير السلوك كبيانات.
بناء جملة الإغلاقات موجز: |parameters| body. يمكن تمرير الإغلاقات إلى الدوال، وتخزينها في متغيرات، وإرجاعها من الدوال. من المهم تذكر السمات الثلاث التي تتحكم في الالتقاط: Fn (الاستعارة)، FnMut (الاستعارة القابلة للتغيير)، و FnOnce (الاستهلاك).
fn main() {
// إغلاق بسيط
let add_one = |x: i32| x + 1;
println!("{}", add_one(5)); // 6
// التقاط متغير
let factor = 10;
let multiply = |x| x * factor;
println!("{}", multiply(3)); // 30
// إغلاق مع سمة FnOnce
let name = String::from("Rust");
let greet = move || {
println!("مرحباً, {}!", name); // name تم نقله
};
greet();
// println!("{}", name); // خطأ! name لم يعد متاحاً
}
الكلمة المفتاحية move تفرض الالتقاط بالقيمة (النقل)، وهو أمر ضروري غالباً عند العمل مع تعدد الخيوط أو عند تمرير الإغلاق إلى سياق آخر.
المكررات (iterators): المعالجة الكسولة وسلاسل الدوال
من أقوى جوانب الأسلوب الوظيفي في Rust هو محول المكررات القوي. بدلاً من الحلقات for الصريحة وتغيير المتغيرات، تستخدم سلاسل من الدوال: map، filter، fold، collect وغيرها. المكررات في Rust كسولة (lazy) - فهي لا تنفذ العمليات الحسابية حتى يتم استدعاء دالة مستهلكة (مثل collect() أو for_each()).
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
// النهج الوظيفي: التصفية والتحويل
let result: Vec<i32> = numbers
.iter() // إنشاء مكرر
.filter(|&x| x % 2 == 0) // الاحتفاظ فقط بالأعداد الزوجية
.map(|x| x * x) // التربيع
.collect(); // التجميع في متجه
println!("{:?}", result); // [4, 16, 36]
// المجموع باستخدام fold (الاختزال)
let sum = numbers.iter().fold(0, |acc, x| acc + x);
println!("المجموع: {}", sum); // 21
// التحقق من الشرط باستخدام any و all
let has_even = numbers.iter().any(|x| x % 2 == 0);
println!("يوجد أعداد زوجية: {}", has_even); // true
}
استخدام المكررات يجعل الكود تصريحياً (declarative)، قابلاً للقراءة، وأقل عرضة للأخطاء المتعلقة بالفهرسة وحدود المصفوفات. غالباً ما يقوم مترجم Rust بتحسين سلاسل المكررات إلى كود آلة فعال، يمكن مقارنته بالحلقات اليدوية.
مطابقة الأنماط (pattern matching) والتفكيك (destructuring)
مطابقة الأنماط (match) هي قلب التحكم الوظيفي في التدفق في Rust. تسمح بمعالجة آمنة لجميع الخيارات الممكنة للبيانات، خاصةً عند دمجها مع أنواع البيانات الجبرية (enum). يمكن أن تكون الأنماط متداخلة، وتتضمن شروطاً (guards)، وتفكيك الهياكل (structs)، والمجموعات (tuples)، والتعدادات (enums).
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn handle_