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:
// ============================================
// 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، وحالة الذعر:
graph TB
A["Result<T, E>"] --> 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. ما ستتعلمه
Result<T, E>التعداد: النوع الأساسي لمعالجة الأخطاء في لغة Rust، وله نوعان:Ok(T)وErr(E)unwrap/expect: طرق الحصول السريع على القيم والمخاطر المرتبطة بها?Operator: صيغة موجزة لنقل الأخطاء بين الدوالmatchمعالجة الأخطاء: معالجة دقيقة لمختلف الأخطاء- أنواع الأخطاء المخصصة: قم بتنفيذ
Display+Debugلجعل رسائل الأخطاء أكثر وضوحًا panic!مقابل استراتيجيات اختيار قيمة الإرجاع في حالات الخطأ: أي نهج يجب استخدامه في أي سيناريو
4. المفاهيم الأساسية
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» (مستوى الصعوبة ⭐)
// ============================================
// 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");
}
الناتج:
=== 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) ▶ المثال:? عملية حسابية — خطأ الانتشار المتسلسل (الصعوبة ⭐⭐)
// ============================================
// ? 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 موجود وغير فارغ):
--- 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) ▶ المثال:أنواع الأخطاء المخصصة — تنفيذ العرض + التصحيح (مستوى الصعوبة ⭐⭐)
// ============================================
// 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),
}
}
}
الناتج:
[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! مقابل استراتيجيات التعامل مع قيم الأخطاء المرجعة (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
الناتج:
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 تلقائيًا |
// 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) ▶ المثال: تمرين شامل — انتشار الأخطاء واستعادتها عبر طبقات متعددة (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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());
}
الناتج:
=== 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 تتطلب كليهما.📖 ملخص
Result<T, E>هو جوهر معالجة الأخطاء في لغة Rust — حيث يشيرOk(T)إلى النجاح، ويشيرErr(E)إلى الفشل، ويشترط المُجمِّع عليك معالجة كلتا الحالتين- يُعد
?«تسهيلًا نحويًا» لانتشار الأخطاء — فهو يُرجع تلقائيًاErrفي حالة الفشل، ويسترد القيمة منOkفي حالة النجاح، ويقوم تلقائيًا بتحويل نوع الخطأ. match/unwrap_or/unwrap_or_elseتوفر معالجة الأخطاء بمستويات مختلفة من الدقة — بدءًا من التفرع الدقيق وصولاً إلى الحل البديل السريع- أنواع الأخطاء المخصصة: اجعل رسائل الأخطاء واضحة وسهلة القراءة من خلال تطبيق
Display + Debug، واستخدمFrom traitلإجراء تحويل الأنواع. - يُستخدم
panic!للأخطاء التي لا يمكن استردادها (أخطاء البرمجة)، ويُستخدمResultللأخطاء التي يمكن استردادها (استثناءات البيئة الخارجية) — وهذه هي الاستراتيجية الأساسية لمعالجة الأخطاء في لغة Rust anyhowوthiserrorهما أداتان قياسيتان لمعالجة الأخطاء على مستوى الإنتاج — حيث تُبسط الأولى عملية الانتشار، بينما تُبسط الثانية عملية التعريف
📝 تمارين
-
الصعوبة ⭐: اكتب دالة
fn parse_age(input: &str) -> Result<u8, String>تقوم بتحليل سلسلة نصية لتحويلها إلى عمر (0–150). إذا فشل التحليل أو كانت القيمة خارج النطاق، فقم بإرجاع رسالة الخطأ المقابلة. فيmain، استخدمmatchللتعامل مع ثلاث حالات: عمر صالح، وإدخال غير رقمي، وقيمة خارج النطاق. -
الصعوبة ⭐⭐: اكتب سيناريو يتضمن استدعاءات متداخلة للدوال — حيث تستدعي الدالة «أ» الدالة «ب»، وتستدعي الدالة «ب» الدالة «ج»، وقد تفشل كل دالة في أي مستوى. استخدم عامل
?لنقل الأخطاء. السيناريو:read_user_file()->parse_user_data()->validate_age(). يعرض كل مستوى نفس نوع الخطأ المخصصUserDataError(المُعرَّف باستخدام تعداد)، والذي يتضمن ثلاثة متغيرات:FileNotFoundوParseFailed(String)وInvalidAge(i32). -
الصعوبة ⭐⭐⭐: صمم آلة حاسبة صغيرة تدعم أربع عمليات حسابية:
add،subtract،multiply، وdivide. تنتقل الأخطاء من جميع العمليات إلى?. المتطلبات:- تعريف قائمة
CalcError(DivideByZero،Overflow،InvalidOperator(String)) - تنفيذ
Display+Debug - استخدم
?لربط عدة عمليات معًا: تعبيرات سلاسل مثلcalculate("10 + 5 * 2") - نصيحة: قم أولاً بتقسيم السلسلة حسب المسافات، ثم عالج كل جزء على حدة باستخدام
foldأو حلقة تكرار.
- تعريف قائمة