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

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

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

ابدأ الدورة

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

\n\n

على عكس العديد من اللغات حيث الاستثناءات هي الآلية القياسية (مثل try/catch في Python أو Java أو C++)، تقدم Rust نهجًا مختلفًا جوهريًا. لا توجد استثناءات بالمعنى الكلاسيكي هنا. بدلاً من ذلك، تستخدم اللغة نظام أنواع قوي لتمثيل الأخطاء ومعالجتها. قد يبدو هذا معقدًا للمبتدئين، ولكن على المستوى المتقدم ستقدر الأمان والقابلية للتنبؤ التي يوفرها هذا النهج. في هذه المقالة، سنشرح كيفية العمل مع الأخطاء في Rust بشكل احترافي: من الأساسيات Result و Option إلى الكريتات المتقدمة anyhow و thiserror، وسنقوم أيضًا بعمل مقارنات مع try/catch المألوف.

\n\n

1. الأساس: Result وOption والعامل ?

\n\n

أساس معالجة الأخطاء في Rust هو التعداد Result<T, E>. يمكن أن يكون إما Ok(T) (نجاح) أو Err(E) (خطأ). يمكن اعتبار العامل ? بمثابة النظير لـ try/catch. فهو يبسط تمرير الخطأ إلى الأعلى: إذا كانت القيمة Err، تقوم الدالة بإرجاعها فورًا؛ إذا كانت Ok، فإنها تستخرج القيمة.

\n\n
use std::fs::File;\nuse std::io::Read;\n\nfn read_file(path: &str) -> Result<String, std::io::Error> {\n    let mut file = File::open(path)?; // نظير try: تمرير الخطأ\n    let mut content = String::new();\n    file.read_to_string(&mut content)?;\n    Ok(content)\n}
\n\n

في هذا المثال، يمكن لكل من File::open و read_to_string إرجاع Err. العامل ? يحل محل match المرهق ويجعل الكود نظيفًا، مثل try/catch في اللغات الأخرى. ومع ذلك، على عكس الاستثناءات، فإنك تحدد نوع الخطأ صراحةً في توقيع الدالة - مما يعزز قابلية القراءة والأمان.

\n\n

يُستخدم التعداد Option<T> للحالات التي قد تكون فيها القيمة مفقودة (نظير None في Python). يعمل العامل ? معه أيضًا:

\n\n
fn get_first_element(vec: &Vec<i32>) -> Option<&i32> {\n    vec.first()? // إذا كان المتجه فارغًا، نعيد None\n}
\n\n

2. العمل المتقدم مع أنواع الأخطاء: السمات From وError

\n\n

عندما تستدعي دالة عدة عمليات بأنواع أخطاء مختلفة (مثل الإدخال/الإخراج والتحليل)، تظهر مشكلة: كيف نعيد نوعًا واحدًا؟ الحل هو السمة From ونوع خطأ مخصص.

\n\n

لننشئ هيكل الخطأ الخاص بنا ونطبق عليه السمة std::error::Error (تلقائيًا عبر #[derive(Debug)] وتنفيذ Display يدويًا):

\n\n
use std::fmt;\nuse std::io;\n\n#[derive(Debug)]\nenum MyError {\n    Io(io::Error),\n    Parse(std::num::ParseIntError),\n}\n\nimpl fmt::Display for MyError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            MyError::Io(e) => write!(f, "IO error: {}", e),\n            MyError::Parse(e) => write!(f, "Parse error: {}", e),\n        }\n    }\n}\n\nimpl std::error::Error for MyError {}\n\n// ننفذ From للتحويل التلقائي\nimpl From<io::Error> for MyError {\n    fn from(error: io::Error) -> Self {\n        MyError::Io(error)\n    }\n}\n\nimpl From<std::num::ParseIntError> for MyError {\n    fn from(error: std::num::ParseIntError) -> Self {\n        MyError::Parse(error)\n    }\n}\n\nfn process_data(path: &str) -> Result<i32, MyError> {\n    let content = std::fs::read_to_string(path)?; // يتم التحويل تلقائيًا إلى MyError::Io\n    let number: i32 = content.trim().parse()?;    // يتم التحويل تلقائيًا إلى MyError::Parse\n    Ok(number)\n}
\n\n

الآن، تقوم الدالة process_data بإرجاع نوع واحد MyError، ويقوم العامل ? بتحويل الأخطاء تلقائيًا بفضل السمة المطبقة

المدونات

توصيات الكتب