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

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

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

ابدأ الدورة
```html

مقدمة: لماذا لا تستخدم Rust أسلوب try/catch؟



في معظم لغات البرمجة (JavaScript, Python, Java) تعتمد معالجة الأخطاء على آلية الاستثناءات وبنية try/catch. لكن في Rust هذا الأسلوب غير موجود. بدلاً من ذلك، يستخدم المطورون نظام أنواع قوي يجعل الأخطاء جزءاً من توقيع الدالة. هذا ليس صدفة، بل قرار معماري مدروس: تسعى Rust إلى قابلية التنبؤ والأمان في مرحلة التجميع، مستبعدةً حالات تعطل البرنامج الخفية.



في هذه المقالة سنشرح:

  • لماذا تخلت Rust عن try/catch؛
  • كيف تعمل الأدوات الأساسية: Option, Result, unwrap(), expect()؛
  • ما هو العامل ? (علامة الاستفهام)؛
  • متى نستخدم panic! و try! (القديم)؛
  • الممارسات الحديثة لمعالجة الأخطاء في Rust 2024.


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



نوع Option: عندما قد تكون القيمة غائبة



Option<T> هو تعداد (enum) يمثل إما قيمة (Some(T)) أو غيابها (None). إنه مثالي للحالات التي لا يحدث فيها خطأ بالمعنى التقليدي، لكن النتيجة قد تكون فارغة.



fn ابحث_عن_مستخدم(id: u32) -> Option<String> {    let قاعدة = vec!["Alice".to_string(), "Bob".to_string()];    if id < قاعدة.len() {        Some(قاعدة[id].clone())    } else {        None    }}

fn main() { match ابحث_عن_مستخدم(1) { Some(اسم) => println!("تم العثور على: {}", اسم), None => println!("لم يتم العثور على المستخدم"), }}


نوع Result: نجاح أو خطأ



Result<T, E> هو الأداة الأساسية لمعالجة الأخطاء. وهو أيضاً enum: Ok(T) (نجاح) أو Err(E) (خطأ).



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

fn اقرأ_ملف(مسار: &str) -> Result<String, std::io::Error> { let mut ملف = File::open(مسار)?; let mut محتوى = String::new(); ملف.read_to_string(&mut محتوى)?; Ok(محتوى)}


2. العامل ? (علامة الاستفهام) — بديل try/catch



العامل ? هو الطريقة الأساسية لتمرير الأخطاء للأعلى في Rust. إنه يغني عن بنى match الطويلة ويجعل الكود نظيفاً. إذا كانت النتيجة Ok، يتم استخراج القيمة وتعيينها للمتغير. إذا كانت Err، تقوم الدالة فوراً بإرجاع هذا الخطأ.



use std::fs;use std::io;

fn انسخ_ملف(مصدر: &str, وجهة: &str) -> io::Result<()> { let بيانات = fs::read(مصدر)?; // إذا كان خطأ — خروج fs::write(وجهة, &بيانات)?; // إذا كان خطأ — خروج Ok(())}

fn main() -> io::Result<()> { انسخ_ملف("source.txt", "dest.txt")?; println!("تم نسخ الملف بنجاح"); Ok(())}


مهم: العامل ? يعمل فقط في الدوال التي تُرجع Result أو Option أو أنواعاً تطبق trait FromResidual.



3. panic!, unwrap و expect — عندما يكون الخطأ قاتلاً



ماكرو panic!



panic!() يسبب إنهاءً طارئاً للبرنامج (stack unwinding). يُستخدم فقط في الحالات التي يكون فيها استمرار التنفيذ مستحيلاً أو غير مجدٍ: أخطاء في منطق البرنامج، انتهاك الثوابت، الاختبارات.



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


الطريقتان unwrap() و expect()



unwrap() هي طريقة سريعة للحصول على القيمة من Option أو Result. إذا كان بداخلها None أو Err — فسيتم استدعاء panic!. expect() مشابهة لكنها تسمح بإضافة رسالة خطأ مخصصة.



fn main() {    let نتيجة: Result<i32, &str> = Ok(10);    let قيمة = نتيجة.unwrap(); // 10

let نتيجة_خطأ: Result<i32, &str> = Err("حدث خطأ"); // نتيجة_خطأ.unwrap(); // سيحدث panic!

let قيمة_آمنة = نتيجة_خطأ.expect("فشل غير متوقع");}


4. معالجة الأخطاء في Rust 2024: الممارسات الحديثة



في Rust الحديثة، يُنصح بما يلي:

  • استخدام ? لنقل الأخطاء إلى الأعلى كلما أمكن؛
  • تجنب unwrap() و expect() في كود الإنتاج — استخدم معالجة الأخطاء الصريحة؛
  • استخدام panic! فقط للأخطاء القاتلة الحقيقية (مثل فشل الثوابت)؛
  • استخدام مكتبات مثل anyhow للأخطاء البسيطة و thiserror للأخطاء المعقدة؛
  • تحديد أنواع أخطاء مخصصة باستخدام enum لتوثيق حالات الفشل المحتملة.


use std::fs;use std::io;

#[derive(Debug)]enum خطأ_مخصص { خطأ_ملف(io::Error), بيانات_غير_صالحة(String),}

fn معالجة_متقدمة(مسار: &str) -> Result<String, خطأ_مخصص> { let محتوى = fs::read_to_string(مسار) .map_err(|e| خطأ_مخصص::خطأ_ملف(e))?; if محتوى.is_empty() { return Err(خطأ_مخصص::بيانات_غير_صالحة(\"الملف فارغ\".into())); } Ok(محتوى)}

fn main() -> Result<(), خطأ_مخصص> { let بيانات = معالجة_متقدمة("example.txt")?; println!("المحتوى: {}", بيانات); Ok(())}


الخلاصة



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

```

المدونات

توصيات الكتب