معالجة الأخطاء في Rust: من unwrap إلى التقنيات المتقدمة

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

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

ابدأ الدورة

معالجة الأخطاء في Rust: من unwrap إلى التقنيات المتقدمة



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



1. لماذا تخلى Rust عن try/catch؟



في اللغات التي تستخدم الاستثناءات، يمكن مقاطعة تدفق التنفيذ في أي لحظة. هذا يعقد تحليل الكود وقد يؤدي إلى تسرب الموارد. يقدم Rust طريقتين رئيسيتين:



  • الأخطاء القابلة للاسترداد — النوع Result<T, E> (نجاح Ok(T) أو خطأ Err(E)).
  • الأخطاء غير القابلة للإصلاح — الماكرو panic!، الذي يوقف البرنامج.


العامل ? هو المكافئ لـ try في Rust. يقوم باستخراج القيمة من Ok أو يعيد Err فورًا من الدالة.



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

fn read_username(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; // إذا كان هناك خطأ — نعيده let mut username = String::new(); file.read_to_string(&mut username)?; // مرة أخرى "try" Ok(username.trim().to_string())}


2. إنشاء أنواع الأخطاء الخاصة بك



للمشاريع المعقدة، لا يكفي std::io::Error وحده. أفضل ممارسة هي تعريف نوع الخطأ الخاص بك باستخدام التعداد (enum). هذا يسمح بتجميع أنواع مختلفة من الأخطاء وإضافة سياق.



use std::fmt;

#[derive(Debug)]enum MyAppError { IoError(std::io::Error), ParseError(String), NetworkError { url: String, status: u16 },}

impl fmt::Display for MyAppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { MyAppError::IoError(e) => write!(f, "IO error: {}", e), MyAppError::ParseError(s) => write!(f, "Parse error: {}", s), MyAppError::NetworkError { url, status } => { write!(f, "Network error for {}: status {}", url, status) } } }}

impl std::error::Error for MyAppError {} // ننفذ trait Error


الآن يمكن استخدام ? للتحويل التلقائي، إذا قمنا بتنفيذ trait From:



impl From<std::io::Error> for MyAppError {    fn from(err: std::io::Error) -> MyAppError {        MyAppError::IoError(err)    }}

fn process_file(path: &str) -> Result<(), MyAppError> { let content = std::fs::read_to_string(path)?; // يتم التحويل تلقائيًا if content.is_empty() { return Err(MyAppError::ParseError("empty file".into())); } Ok(())}


3. الكريتات المتقدمة: thiserror و anyhow



كتابة جميع تطبيقات Display و From يدويًا أمر شاق. لهذا الغرض، يوجد كريتتان شائعتان:



thiserror — للمكتبات

كريت thiserror يولد تطبيق std::error::Error عبر derive-macro. مثالي للمكتبات حيث تحتاج إلى وصف دقيق للأخطاء.



use thiserror::Error;

#[derive(Error, Debug)]pub enum DataError { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Invalid UTF-8: {0}")] Utf8(#[from] std::str::Utf8Error), #[error("Custom failure: {details}")] Custom { details: String },}

// الآن يمكننا كتابة:fn parse_data(bytes: &[u8]) -> Result<String, Da

المدونات

توصيات الكتب