مقدمة: لماذا الكود النظيف في Rust ليس مجرد أسلوب
Rust هي لغة تجمع بين أداء C++ وأمان الذاكرة، لكن نظامها الفريد للملكية (ownership) والاقتراض (borrowing) يتطلب نهجاً خاصاً لكتابة الكود. مبادئ Clean Code التي صاغها روبرت مارتن تكتسب في Rust بُعداً جديداً: هنا نقاء الكود يرتبط مباشرة بالصحة والأداء وسهولة الصيانة.
في هذه المقالة، سنشرح كيفية تطبيق تعابير Rust (idioms) لإنشاء كود موثق ذاتياً وآمن وفعال. سترى كيف تستبدل الأخطاء النمطية للمبتدئين بحلول أنيقة متبعة في مجتمع Rust.
1. معالجة الأخطاء الاصطلاحية: من unwrap() إلى الأنماط التعبيرية
إحدى أولى المشكلات التي يواجهها المبتدئون هي الإفراط في استخدام unwrap(). في Clean Code، الخطأ ليس استثناءً، بل جزء من البنية. توفر Rust أنواعاً قوية مثل Option و Result للمعالجة الصريحة للحالات الحدودية.
مثال سيء (كود غير نظيف)
fn parse_number(input: &str) -> i32 { input.parse::<i32>().unwrap()}
fn main() { let val = parse_number("ليس رقماً"); println!("{}", val); // ذعر (Panic)!}مثال جيد (كود نظيف)
use std::num::ParseIntError;
fn parse_number(input: &str) -> Result<i32, ParseIntError> { input.parse::<i32>()}
fn main() { match parse_number("42") { Ok(n) => println!("الرقم: {}", n), Err(e) => eprintln!("خطأ: {}", e), }}لماذا هذا هو Clean Code؟ الدالة تُعلم صراحةً أنها قد تفشل. الكود المستدعي هو من يقرر كيفية معالجة الفشل — سواء بتسجيله، أو إعادة المحاولة، أو تمرير الخطأ للأعلى.
تقنية متقدمة: العامل ?
fn read_config(path: &str) -> Result<String, std::io::Error> { let content = std::fs::read_to_string(path)?; Ok(content)}العامل ? هو بديل أنيق لتعبيرات match الطويلة. فهو يُعيد الخطأ إلى الأعلى إذا كانت النتيجة Err، ويستخرج القيمة إذا كانت Ok. هذا يقلل من التداخل ويحسن قابلية القراءة.
2. الملكية والاقتراض: صمم البنية، ولا تحارب المترجم
قد يبدو نظام الملكية في Rust مقيداً، لكنه في الواقع يساعد في كتابة كود نظيف. تبدأ المشاكل عندما يحاول المطور "خداع" المترجم باستخدام clone() أو Rc<RefCell> دون داع.
النمط المضاد: الاستنساخ المفرط
fn process_data(data: Vec<String>) { for item in data.clone() { // لماذا الاستنساخ؟ println!("{}", item); } // data لم يعد مستخدماً}الحل النظيف: الاقتراض بالمرجع
fn process_data(data: &[String]) { for item in data { println!("{}", item); }}
fn main() { let my_data = vec!["a".to_string(), "b".to_string()]; process_data(&my_data); // الملكية تبقى مع main println!("المتجه لا يزال متاحاً: {:?}", my_data);}مبدأ Clean Code: الحد الأدنى من نطاق رؤية التعديلات. إذا كانت الدالة لا يجب أن تعدل البيانات — استقبل مرجعاً غير قابل للتغيير &T. إذا كنت بحاجة للتعديل — استخدم &mut T. هذا يجعل الكود قابلاً للتنبؤ وآمناً.
تعابير Rust: نقل الملكية للتحويل
fn to_uppercase(mut text: String) -> String { text.make_ascii_uppercase(); text // إعادة الملكية}هنا الدالة "تستهلك" السلسلة النصية، تعدلها، وتعيدها. هذا فعال (بدون نسخ) وصريح: المستدعي يفهم أن المتغير الأصلي لم يعد صالحاً.