معالجة الأخطاء في Rust: try/catch وما بعدها - دليل شامل

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

معالجة الأخطاء في Rust: التخلي عن try/catch لصالح الأمان



على عكس Python أو Java، لا توجد في Rust بنية try/catch الكلاسيكية. سلك مطورو اللغة مسارًا مختلفًا، مما جعل معالجة الأخطاء جزءًا من نظام الأنواع. هذا ليس نزوة، بل قرارًا أساسيًا يسمح بضمان أمان الذاكرة وإمكانية التنبؤ بعمل البرنامج في مرحلة الترجمة. في هذه المقالة، سنشرح جميع طرق معالجة الأخطاء في Rust: من الأساسيات Result و Option إلى الطرق المتقدمة باستخدام عامل التشغيل ? وأنواع الأخطاء المخصصة.



1. النوعان الرئيسيان: Result و Option



في Rust، تنقسم جميع الأخطاء إلى معسكرين: الأخطاء القابلة للاسترداد (يتم التعامل معها عبر Result) و الأخطاء غير القابلة للاسترداد (تؤدي إلى panic!). يوجد أيضًا نوع Option للحالات التي قد تكون فيها القيمة مفقودة.



نوع Result



Result<T, E> هو تعداد (enum) يمكن أن يكون إما Ok(T) (نجاح مع بيانات) أو Err(E) (خطأ مع وصف). هذه هي الأداة الرئيسية لمعالجة الأخطاء المتوقعة: أخطاء الإدخال/الإخراج، والتحليل، وطلبات الشبكة.



use std::fs::File;use std::io::Read;

fn read_file_content(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; let mut content = String::new(); file.read_to_string(&mut content)?; Ok(content)}


نوع Option



Option<T> هو enum آخر يأخذ القيم Some(T) (توجد قيمة) أو None (لا توجد قيمة). يُستخدم عندما يكون غياب البيانات موقفًا طبيعيًا، وليس خطأ.



fn find_user(id: u32) -> Option<String> {    let users = vec!["Alice".to_string(), "Bob".to_string()];    users.get(id as usize).cloned()}

fn main() { match find_user(0) { Some(name) => println!("تم العثور على المستخدم: {}", name), None => println!("لم يتم العثور على المستخدم"), }}


2. عامل التشغيل ? - بديل أنيق لـ try/catch



إذا كان في Rust try/catch، لكان يبدو ضخمًا. بدلاً من ذلك، تقدم اللغة عامل التشغيل ?. يعمل على النحو التالي: إذا كانت النتيجة Ok، يتم استخراج القيمة وتمريرها؛ إذا كانت Err، يتم إرجاع الخطأ فورًا من الدالة.



use std::num::ParseIntError;

fn parse_and_double(input: &str) -> Result<i32, ParseIntError> { let value: i32 = input.parse()?; // إذا كان خطأ - return Err فورًا Ok(value * 2)}

fn main() { match parse_and_double("42") { Ok(result) => println!("النتيجة: {}", result), Err(e) => println!("خطأ في التحليل: {}", e), }}


يمكن استخدام عامل التشغيل ? فقط في الدوال التي تُرجع Result أو Option. وهذا يجبر المطور على تحديد أن الدالة قد تنتهي بخطأ - على عكس الاستثناءات في Python التي يمكن أن "تظهر" من أي مكان.



3. متى نستخدم panic! و unwrap



ليست كل الأخطاء بحاجة إلى المعالجة. panic! هو ماكرو للأخطاء غير القابلة للاسترداد. يوقف البرنامج ويفك المكدس (stack unwind). استخدمه فقط في الحالات الاستثنائية:



  • الحالات الطارئة: انتهاك الثوابت التي لا يمكن إصلاحها.
  • الأمثلة والنماذج الأولية: في كود تعليمي أو نصوص برمجية ليوم واحد.
  • الاختبارات: للتحقق من أن الكود يتعطل في الأماكن المتوقعة.


fn divide(a: i32, b: i32) -> i32 {    if b == 0 {        panic!("القسمة على صفر! a={}, b={}", a, b);    }    a / b}


unwrap() و expect()

المدونات

توصيات الكتب