Rust: معالجة أخطاء Rust: `Result` والمُشغِّل `?`

آخر تحديث: 2026-08-26

تُعد معالجة الأخطاء إحدى أبرز ميزات تصميم لغة Rust — فهي لا تعتمد على الاستثناءات، بل تستخدم نظام الأنواع لترميز «الفشل المحتمل» في نوع القيمة المرجعة، ويضمن المُترجم ألا تغفل أي أخطاء أبدًا.

لا تحتوي لغة Rust على try-catch أو throw. وبدلاً من ذلك، تستخدم قائمة Result<T, E> والمُشغِّل ? — اللذين يفصلان معالجة الأخطاء عن منطق العمل العادي، مما ينتج عنه كود آمن وموجز في آن واحد.


1. قصة حول كيفية التعامل مع المشكلات بدءًا من طلب الطعام الجاهز وحتى استلامه

(1) عملية توصيل الطعام في الواقع

يعمل شياو مينغ اليوم ساعات إضافية، وقد قرر أن يطلب طعامًا جاهزًا. وهناك عدة مراحل في هذه العملية قد تسوء فيها الأمور:

الخطوة الإجراء العادي الأخطاء المحتملة
1. البحث عن مطعم فتح تطبيق توصيل الطعام والبحث عن مطعم لا يمكن العثور على مطعم
2. تحميل القائمة تصفح عناصر القائمة فشل تحميل القائمة (انتهت مهلة الاتصال بالشبكة)
3. إرسال الدفع دفع فشل الدفع (رصيد غير كافٍ)
4. في انتظار الإعداد انتظر 30 دقيقة المتجر يلغي الطلب
5. الاستلام استلام الطلب الجاهز اكتشاف أن الطلب قد تم إلغاؤه عند الاستلام

قد تنجح كل خطوة (Ok) أو تفشل (Err). دعونا نضع نموذجًا لذلك باستخدام نهج لغة Rust:

RUST
// ============================================
// Use Result to simulate every step of ordering takeout
// ============================================

// Defining Possible Errors
#[derive(Debug)]
enum OrderError {
    RestaurantNotFound,
    MenuLoadFailed,
    PaymentFailed(String),
    OrderCancelled,
}

// Simulate Store Search
fn find_restaurant(name: &str) -> Result<String, OrderError> {
    let available = vec!["PizzaHouse", "SushiBar", "NoodleShop"];
    if available.contains(&name) {
        Ok(format!("Found: {}", name))
    } else {
        Err(OrderError::RestaurantNotFound)
    }
}

// Simulate the Load Menu
fn load_menu(restaurant: &str) -> Result<Vec<&str>, OrderError> {
    if restaurant.contains("Pizza") {
        Ok(vec!["Margherita", "Pepperoni", "Hawaiian"])
    } else {
        Err(OrderError::MenuLoadFailed)
    }
}

// Simulated Payment
fn process_payment(amount: f64) -> Result<String, OrderError> {
    if amount < 100.0 {
        Ok(format!("Paid: ${:.2}", amount))
    } else {
        Err(OrderError::PaymentFailed("Insufficient balance".into()))
    }
}

fn main() {
    // Go through the entire process from start to finish
    let restaurant = find_restaurant("PizzaHouse");
    match restaurant {
        Ok(msg) => println!("Step 1: {}", msg),
        Err(e) => println!("Step 1 failed: {:?}", e),
    }
}

في كل خطوة، تشير قيمة الإرجاع Result<T, E>Ok(T) إلى النجاح، بينما تشير Err(E) إلى الفشل. يجب على المستدعي التعامل بشكل صريح مع كلا الاحتمالين؛ فليس من الممكن «نسيان معالجة الأخطاء».



2. الرسوم التخطيطية المفاهيمية

يوضح مخطط التدفق التالي الخاص بـ Mermaid المسارات الأربعة الرئيسية لمعالجة أخطاء Result<T, E>: المعالجة التفصيلية عبر match، وانتشار العوامل عبر ?، والتقييم السريع عبر unwrap/expect، وحالة الذعر:

100%
graph TB
    A["Result&lt;T, E&gt;"] --> B["Ok(T)<br/>Success"]
    A --> C["Err(E)<br/>Failure"]

    B --> D["Continue execution<br/>Standard Procedure"]

    C --> E["match Processing<br/>Fine-Grained Branch Control"]
    C --> F["? Operators<br/>Propagation Error"]
    C --> G["unwrap / expect<br/>Quick Value Retrieval"]

    E --> H["For different errors<br/>Handle them separately"]
    F --> I["Caller Function<br/>Receive Err"]
    G --> J["panic!<br/>Program Crash"]

    H --> K["Restore Default Values<br/>Or retry logic"]
    I --> L["match in main<br/>Centralized Processing"]

    style A fill:#e1f5fe,stroke:#0288d1
    style B fill:#c8e6c9,stroke:#388e3c
    style C fill:#ffcdd2,stroke:#d32f2f
    style J fill:#ffcdd2,stroke:#d32f2f


3. ما ستتعلمه



4. المفاهيم الأساسية

100%
graph TB
    A[Error Handling Strategies] --> B[Recoverable errors<br>Recoverable]
    A --> C[Unrecoverable error<br>Unrecoverable]

    B --> D["Result<T, E>"]
    D --> E["Ok(T) Success Score"]
    D --> F["Err(E) Error value"]

    F --> G["match Precision Processing"]
    F --> H["? Top-down communication"]
    F --> I["unwrap/expect Quick Value Retrieval<br>(There are risks)"]

    C --> J["panic!"]
    J --> K["The program crashed and exited"]
    J --> L["Applicable:Bug/Irreversible state"]

    B -.-> M["Custom Error Types"]
    M --> N["Implementation Display + Debug"]
    M --> O["From trait Convert"]

(1) مقارنة بين أربع استراتيجيات للتعامل مع الأخطاء

الاستراتيجية حالة الاستخدام المزايا العيوب
panic! أخطاء لا يمكن إصلاحها (مثل تجاوز حدود المصفوفة أو فشل عمليات التحقق) فشل سريع للكشف عن المشكلة يتعطل البرنامج على الفور
unwrap / expect تطوير النماذج الأولية / ضمان عدم الفشل كود موجز يدخل في حالة ذعر فور حدوث خطأ — ليس أمراً أنيقاً للغاية
match / if let يتطلب معالجة مختلفة لكل خطأ على حدة تحكم دقيق في منطق معالجة الأخطاء كود مطول
? عامل ينقل الأخطاء بين الدوال؛ ويتم التعامل معها بشكل موحد على المستوى الأعلى الأكثر إيجازًا؛ يحافظ على وضوح المنطق الرئيسي يجب استخدامه في الدوال التي تُرجع نتيجة

(2) Result<T, E> دليل مرجعي سريع

الطريقة التوقيع الغرض السلوك في حالة الفشل
unwrap() Result<T,E> -> T استخراج القيمة من «Ok» panic!
expect(msg) Result<T,E> -> T استرداد القيمة من "Ok" وتخصيص رسالة الطوارئ panic!(msg)
unwrap_or(default) Result<T,E> -> T تُرجع قيمةً في حالة النجاح؛ وتُرجع قيمةً افتراضيةً في حالة الفشل تُرجع قيمةً افتراضيةً
unwrap_or_else(fn) Result<T,E> -> T إرجاع قيمة في حالة النجاح؛ وتنفيذ الإغلاق في حالة الفشل تنفيذ الإغلاق
is_ok() Result<T,E> -> bool التحقق من نجاح العملية
is_err() Result<T,E> -> bool التحقق من وجود عطل
ok() Result<T,E> -> Option<T> تحويل إلى Option None
err() Result<T,E> -> Option<E> إلى Option None
map(fn) Result<T,E> -> Result<U,E> تحويل قيمة النجاح دون تغيير
map_err(fn) Result<T,E> -> Result<T,F> تحويل القيم غير الصحيحة تحويل أنواع الأخطاء
and_then(fn) Result<T,E> -> Result<U,E> العمليات التكميلية في المكالمات المتسلسلة التقييم المباشر


5. أمثلة

(1) ▶ المثال:المعالجة الأساسية لـ «Result» و«match» (مستوى الصعوبة ⭐)

RUST
// ============================================
// Use Result to handle division-by-zero errors
// ============================================

fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    // Use match to handle both success and failure scenarios
    println!("=== match Processing ===");
    match safe_divide(10.0, 2.0) {
        Ok(result) => println!("10 / 2 = {}", result),
        Err(msg) => println!("Error: {}", msg),
    }

    match safe_divide(10.0, 0.0) {
        Ok(result) => println!("10 / 0 = {}", result),
        Err(msg) => println!("Error: {}", msg),
    }

    // Use unwrap_or to provide a default value
    println!("\n=== unwrap_or Default value ===");
    let result1 = safe_divide(10.0, 2.0).unwrap_or(0.0);
    let result2 = safe_divide(10.0, 0.0).unwrap_or(0.0);
    println!("10 / 2 = {}", result1);
    println!("10 / 0 = {} (default)", result2);

    // Use unwrap_or_else to execute a closure
    println!("\n=== unwrap_or_else Closure ===");
    let result3 = safe_divide(10.0, 2.0).unwrap_or_else(|e| {
        eprintln!("Warning: {}, using default", e);
        0.0
    });
    println!("Result: {}", result3);

    // ⚠️ unwrap will panic (Uncomment the code below to try it out)
    // let crash = safe_divide(10.0, 0.0).unwrap();
    // println!("Will not reach here");
}

الناتج:

TEXT 📖 للعرض فقط
=== match Processing ===
10 / 2 = 5
Error: Division by zero

=== unwrap_or Default value ===
10 / 2 = 5
10 / 0 = 0 (default)

=== unwrap_or_else Closure ===
Result: 5

match توفر طريقة معالجة أكثر شمولاً — حيث يمكنك كتابة منطق معالجة منفصل لكل من Ok وErr. أما unwrap_or وunwrap_or_else فهما اختصارات: فهما يوفران قيمًا افتراضية في حالة الفشل. unwrap() هي الأكثر خطورة — فهي تفترض أن النجاح مضمون، وتؤدي إلى تعطل البرنامج فورًا في حالة الفشل.


(2) ▶ المثال:? عملية حسابية — خطأ الانتشار المتسلسل (الصعوبة ⭐⭐)

RUST
// ============================================
// ? Operators: Error propagation with Result
// Only when returning Result can only be used within the function ?
// ============================================

use std::fs::File;
use std::io::{self, Read};

// Read the entire contents of the file
// ? indicates:If File::open Failure,Return Now Err
//         If read_to_string Failure,Return Now Err
fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;          // Open the file,If it fails, return
    let mut content = String::new();
    file.read_to_string(&mut content)?;         // Read the content,If it fails, return
    Ok(content)
}

// Do not use ? An equivalent way of writing——The amount of code has doubled
fn read_file_without_question(path: &str) -> Result<String, io::Error> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    let mut content = String::new();
    match file.read_to_string(&mut content) {
        Ok(_) => Ok(content),
        Err(e) => Err(e),
    }
}

// Chain Call ?——More concise
fn read_file_chain(path: &str) -> Result<String, io::Error> {
    let mut content = String::new();
    File::open(path)?.read_to_string(&mut content)?;
    Ok(content)
}

fn main() {
    // Test files that can and cannot be read, respectively
    let files = vec!["Cargo.toml", "nonexistent.txt"];

    for file in &files {
        match read_file(file) {
            Ok(content) => {
                println!("--- {} ({} bytes) ---", file, content.len());
                println!("{}", &content[..content.len().min(80)]);
            }
            Err(e) => {
                println!("Failed to read '{}': {}", file, e);
            }
        }
    }
}

الناتج (بافتراض أن Cargo.toml موجود وغير فارغ):

TEXT 📖 للعرض فقط
--- Cargo.toml (42 bytes) ---
[package]
name = "demo"
version = "0.1.0"
edition = "2021"

Failed to read 'nonexistent.txt': The system cannot find the file specified. (os error 2)

يُعدُّ المشغل ? جوهر معالجة الأخطاء في لغة Rust: فهو بمثابة اختصار لعبارة «العودة مبكرًا عند حدوث خطأ». ويُعدُّ expr? مكافئًا لـ match expr { Ok(v) => v, Err(e) => return Err(e.into()) }. تجدر الإشارة إلى أن ? يستدعي تلقائيًّا From::from لإجراء تحويل نوع الخطأ — وهذا هو العامل الأساسي في قدرته على الانتشار عبر أنواع الأخطاء المختلفة.


(3) ▶ المثال:أنواع الأخطاء المخصصة — تنفيذ العرض + التصحيح (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Custom Error Types:Make Your Error Messages More Informative
// To be implemented std::fmt::Display + std::fmt::Debug
// ============================================

use std::fmt;
use std::num::ParseIntError;

// Custom Error Enumeration
#[derive(Debug)]
enum AppError {
    /// Input is empty
    EmptyInput,
    /// Failed to parse the number,Includes the original string
    ParseFailed(String),
    /// The value is outside the allowed range
    OutOfRange { value: i32, min: i32, max: i32 },
    /// Division by Zero
    DivisionByZero,
}

// Implementation Display——Control the error messages users see
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::EmptyInput => {
                write!(f, "Input cannot be empty")
            }
            AppError::ParseFailed(input) => {
                write!(f, "Failed to parse '{}' as a number", input)
            }
            AppError::OutOfRange { value, min, max } => {
                write!(f, "Value {} is out of range [{}, {}]", value, min, max)
            }
            AppError::DivisionByZero => {
                write!(f, "Division by zero is not allowed")
            }
        }
    }
}

// Implement From<ParseIntError> — let ? operator convert automatically
impl From<ParseIntError> for AppError {
    fn from(_: ParseIntError) -> Self {
        AppError::ParseFailed("unknown".into())
    }
}

// Processing User Input:Analyze and Verify
fn process_input(input: &str, divisor: i32) -> Result<i32, AppError> {
    if input.is_empty() {
        return Err(AppError::EmptyInput);
    }
    let value: i32 = input.parse().map_err(|_| {
        // Manually Convert Error Types
        AppError::ParseFailed(input.to_string())
    })?;
    if value < -100 || value > 100 {
        return Err(AppError::OutOfRange {
            value,
            min: -100,
            max: 100,
        });
    }
    if divisor == 0 {
        return Err(AppError::DivisionByZero);
    }
    Ok(value / divisor)
}

fn main() {
    let test_cases = vec![
        ("42", 2,    "Normal case"),
        ("",   1,    "Empty input"),
        ("abc", 1,   "Parse error"),
        ("999", 1,   "Out of range"),
        ("50",  0,   "Division by zero"),
    ];

    for (input, divisor, description) in test_cases {
        match process_input(input, divisor) {
            Ok(result) => println!("[{}] OK: {}", description, result),
            Err(e) => println!("[{}] Error: {}", description, e),
        }
    }
}

الناتج:

TEXT 📖 للعرض فقط
[Normal case] OK: 21
[Empty input] Error: Input cannot be empty
[Parse error] Error: Failed to parse 'abc' as a number
[Out of range] Error: Value 999 is out of range [-100, 100]
[Division by zero] Error: Division by zero is not allowed

لتعريف نوع خطأ مخصص، يتعين عليك تنفيذ Display (رسالة الخطأ التي تُعرض للمستخدم) وDebug (إخراج التصحيح الخاص بـ {:?}). يتيح تنفيذ السمة From<T> للمشغل ? تحويل نوع خطأ معين تلقائيًا إلى النوع المخصص الخاص بك — وهذه هي الآلية الأساسية التي تمكّن ? من الانتشار عبر الأنواع.


(4) ▶ المثال:panic! مقابل استراتيجيات التعامل مع قيم الأخطاء المرجعة (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Demo panic! Scenarios Where Error Return Values Are Appropriate
// panic! → Unrecoverable error(Bug/Assertion Failed)
// Result → Recoverable errors(User Input/IOFailure)
// ============================================

// --- Suitable for panic! the scene ---

/// Read the port number from the configuration file
/// If the configuration file is missing,This is part of the program Bug,panic That makes sense.
fn get_default_port() -> u16 {
    // This value is hard-coded in the code.,It's impossible for the resolution to fail.
    "8080"
        .parse()
        .expect("Hardcoded port number is invalid")
}

/// A function that accepts only positive integers
/// Passing a negative number indicates that the caller has Bug,panic Quickly Identify Problems
fn sqrt_unchecked(x: i32) -> f64 {
    if x < 0 {
        panic!("sqrt_unchecked called with negative value: {}", x);
    }
    (x as f64).sqrt()
}

// --- Suitable for Result the scene ---

/// Parsing Numbers from User Input
/// It is normal for users to enter the wrong format.,Should be returned Result
fn parse_user_input(input: &str) -> Result<i32, String> {
    input
        .parse()
        .map_err(|_| format!("'{}' is not a valid integer", input))
}

/// Calculate Body Mass Index(BMI)
/// Weight is 0 A negative number may indicate a data error.,Not a program Bug
fn calculate_bmi(weight_kg: f64, height_m: f64) -> Result<f64, String> {
    if weight_kg <= 0.0 {
        return Err("Weight must be positive".to_string());
    }
    if height_m <= 0.0 {
        return Err("Height must be positive".to_string());
    }
    Ok(weight_kg / (height_m * height_m))
}

fn main() {
    // Scene 1:panic Used for unrecoverable errors
    println!("Default port: {}", get_default_port());

    // Scene 2:panic Used for assertions——Invalid parameter
    let value = 16;
    println!("sqrt({}) = {}", value, sqrt_unchecked(value));

    // Scene 3:Result Used for recoverable errors——User Input
    let inputs = vec!["42", "hello", "-5"];
    for input in inputs {
        match parse_user_input(input) {
            Ok(n) => println!("Parsed: {}", n),
            Err(e) => println!("Parse failed: {}", e),
        }
    }

    // Scene 4:Result Used for business logic validation
    let bmi_cases = vec![
        (70.0, 1.75),
        (0.0, 1.70),
        (65.0, -0.5),
    ];
    for (weight, height) in bmi_cases {
        match calculate_bmi(weight, height) {
            Ok(bmi) => println!("BMI: {:.1}", bmi),
            Err(e) => println!("BMI error: {}", e),
        }
    }

    // Scene 5: expect's panic message helps with debugging
    let numbers = vec![10, 20, 30];
    let first = numbers.first().expect("Vector should not be empty");
    println!("First element: {}", first);
}

الناتج:

TEXT 📖 للعرض فقط
Default port: 8080
sqrt(16) = 4
Parsed: 42
Parse failed: 'hello' is not a valid integer
Parsed: -5
BMI: 22.9
BMI error: Weight must be positive
BMI error: Height must be positive
First element: 10

المبادئ الأساسية لاختيار الاستراتيجية: تُستخدم panic! لـ«الأخطاء الموجودة في البرنامج نفسه» (بيانات غير صالحة مبرمجة بشكل ثابت، معلمات تخالف القواعد المتعارف عليها، تجاوز حدود مؤشر المصفوفة)؛ وتُستخدم Result لـ«الاستثناءات الناتجة عن البيئة الخارجية أو مدخلات المستخدم» (أخطاء الإدخال/الإخراج، فشل التحليل، فشل التحقق من صحة البيانات التجارية). ببساطة: استخدم Result للمشكلات التي يمكنك إصلاحها، واستخدم panic لتلك التي لا يمكنك إصلاحها!



6. مقدمة لمفاهيم anyhow وthiserror

في مشاريع Rust المخصصة للإنتاج، يُستخدم صندوقان من صناديق المجتمع على نطاق واسع لتبسيط معالجة الأخطاء:

الصندوق الاستخدام الرئيسي حالات الاستخدام الميزات الرئيسية
على أي حال انتشار الأخطاء (من منظور المُستدعي) الدالة الرئيسية للتطبيق، أدوات واجهة سطر الأوامر، البرامج النصية anyhow::Result<T>، .context() توفير سياق للخطأ
thiserror تعريف الخطأ (من وجهة نظر مؤلف المكتبة) أنواع الأخطاء في واجهة برمجة التطبيقات العامة للمكتبة تم تنفيذه باستخدام الماكرو derive لتوليد Display + Error تلقائيًا
RUST
// anyhow Style(Concept Examples,No execution required)
// use anyhow::{Context, Result};
//
// fn read_config() -> Result<String> {
//     let content = std::fs::read_to_string("config.toml")
//         .context("Failed to read config file")?;
//     Ok(content)
// }

// thiserror Style(Concept Examples,No execution required)
// use thiserror::Error;
//
// #[derive(Error, Debug)]
// enum MyError {
//     #[error("IO error: {0}")]
//     Io(#[from] std::io::Error),
//
//     #[error("Parse error: {0}")]
//     Parse(#[from] std::num::ParseIntError),
// }

anyhow تشجعك على التركيز على «كيفية التعامل مع الأخطاء» بدلاً من «كيفية تعريف الأخطاء»؛ thiserror تشجعك على استخدام التعليقات التوضيحية بدلاً من كتابة التعليمات البرمجية يدويًّا Display + From. وعادةً ما يُستخدم الاثنان معًا: تستخدم المكتبات thiserror لتعريف أنواع الأخطاء الدقيقة، بينما تستخدم التطبيقات anyhow لنشرها بشكل موحد.


(1) ▶ المثال: تمرين شامل — انتشار الأخطاء واستعادتها عبر طبقات متعددة (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Custom Errors + ? Dissemination + Recovery Strategy
// ============================================

use std::fmt;

#[derive(Debug)]
enum AppError {
    ParseError(String),
    ValidationError(String),
    NotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::ParseError(msg) => write!(f, "Parsing error: {}", msg),
            AppError::ValidationError(msg) => write!(f, "Validation Error: {}", msg),
            AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
        }
    }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self {
        AppError::ParseError(e.to_string())
    }
}

fn parse_age(input: &str) -> Result<u8, AppError> {
    let age: u8 = input.parse().map_err(|_| AppError::ParseError(format!("'{}' Not a valid number", input)))?;
    if age > 150 {
        return Err(AppError::ValidationError(format!("Age {} Unreasonable", age)));
    }
    Ok(age)
}

fn find_user(id: u32) -> Result<String, AppError> {
    let users = [(1, "Alice"), (2, "Bob"), (3, "Charlie")];
    users.iter()
        .find(|(uid, _)| *uid == id)
        .map(|(_, name)| name.to_string())
        .ok_or_else(|| AppError::NotFound(format!("User ID={}", id)))
}

fn process_user(id_str: &str, age_str: &str) -> Result<String, AppError> {
    let id: u32 = id_str.parse().map_err(|_| AppError::ParseError(format!("Invalid ID: '{}'", id_str)))?;
    let age = parse_age(age_str)?;
    let name = find_user(id)?;
    Ok(format!("User: {}, Age: {}", name, age))
}

fn main() {
    let test_cases = [
        ("1", "30"),
        ("2", "200"),
        ("5", "25"),
        ("abc", "30"),
        ("3", "abc"),
    ];

    println!("=== User Processing Test ===");
    for (id, age) in &test_cases {
        match process_user(id, age) {
            Ok(result) => println!("Success: {}", result),
            Err(e) => println!("Failure: {}", e),
        }
    }

    println!("\n=== Batch Processing (Fault-Tolerant) ===");
    let inputs = [("1", "30"), ("5", "25"), ("2", "abc"), ("3", "20")];
    let mut success_count = 0;
    for (id, age) in &inputs {
        match process_user(id, age) {
            Ok(result) => { println!("OK: {}", result); success_count += 1; }
            Err(e) => println!("Skip: {}", e),
        }
    }
    println!("Success: {}/{}", success_count, inputs.len());
}

الناتج:

TEXT 📖 للعرض فقط
=== User Processing Test ===
Success: User: Alice, Age: 30
Failure: Validation Error: Age 200 Unreasonable
Failure: Not found: User ID=5
Failure: Parsing error: Invalid ID: 'abc'
Failure: Parsing error: 'abc' Not a valid number

=== Batch Processing (Fault-Tolerant) ===
OK: User: Alice, Age: 30
Skip: Not found: User ID=5
Skip: Parsing error: 'abc' Not a valid number
OK: User: Charlie, Age: 20
Success: 2/4

إن عملية التعداد المخصص AppError + التحويل From + الانتشار ? تجعل سلسلة معالجة الأخطاء واضحة وموجزة. يقوم map_err بتحويل الأخطاء الأساسية إلى أنواع مخصصة. بالنسبة للمعالجة الدفعية، استخدم match لتحمل الأعطال — فهو لا يوقف الحلقة، بل يسجل حالات الفشل ويواصل العمل.



❓ أسئلة شائعة

س هل يُعتبر unwrap() ممارسة سيئة؟
ج نعم، ما لم تكن متأكدًا من عدم حدوث أي أخطاء.
س ? (المشغل) مقابل match (معالجة الأخطاء) — أيهما يجب أن يكون له الأسبقية؟
ج أعطِ الأولوية لـ ? في انتشار الأخطاء، واستخدم match فقط عندما تتطلب المعالجة دقة أكبر.
س تحتوي الدالة الخاصة بي على أنواع متعددة من الأخطاء (أخطاء الإدخال/الإخراج، أخطاء التحليل، أخطاء الأعمال). كيف يمكن لـ ? أن تنقلها بشكل موحد؟
ج من خلال التحويل التلقائي بواسطة السمة From.
س هل يمكن التقاط panic!؟
ج نعم، ولكن لا ينبغي استخدامه كطريقة قياسية لمعالجة الأخطاء.
س لماذا يتعين على أنواع الأخطاء المخصصة أن تنفذ كل من Display وDebug؟
ج لأن سمة Error في لغة Rust تتطلب كليهما.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: اكتب دالة fn parse_age(input: &str) -> Result<u8, String> تقوم بتحليل سلسلة نصية لتحويلها إلى عمر (0–150). إذا فشل التحليل أو كانت القيمة خارج النطاق، فقم بإرجاع رسالة الخطأ المقابلة. في main، استخدم match للتعامل مع ثلاث حالات: عمر صالح، وإدخال غير رقمي، وقيمة خارج النطاق.

  2. الصعوبة ⭐⭐: اكتب سيناريو يتضمن استدعاءات متداخلة للدوال — حيث تستدعي الدالة «أ» الدالة «ب»، وتستدعي الدالة «ب» الدالة «ج»، وقد تفشل كل دالة في أي مستوى. استخدم عامل ? لنقل الأخطاء. السيناريو: read_user_file() -> parse_user_data() -> validate_age(). يعرض كل مستوى نفس نوع الخطأ المخصص UserDataError (المُعرَّف باستخدام تعداد)، والذي يتضمن ثلاثة متغيرات: FileNotFound وParseFailed(String) وInvalidAge(i32).

  3. الصعوبة ⭐⭐⭐: صمم آلة حاسبة صغيرة تدعم أربع عمليات حسابية: add، subtract، multiply، وdivide. تنتقل الأخطاء من جميع العمليات إلى ?. المتطلبات:

    • تعريف قائمة CalcError (DivideByZero، Overflow، InvalidOperator(String))
    • تنفيذ Display + Debug
    • استخدم ? لربط عدة عمليات معًا: تعبيرات سلاسل مثل calculate("10 + 5 * 2")
    • نصيحة: قم أولاً بتقسيم السلسلة حسب المسافات، ثم عالج كل جزء على حدة باستخدام fold أو حلقة تكرار.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%