فهرست مطالب
اگر با زبانهایی مثل جاوا، پایتون یا سیشارپ کار کرده باشید، احتمالاً عادت دارید هنگام بروز خطا از سازوکار استثنا (Exception) و بلوکهای try-catch استفاده کنید. در چنین سیستمی اگر فراموش کنید خطایی را مهار کنید، برنامه در زمان اجرا متوقف میشود. این توقف ناگهانی هزینههای سنگینی در محیط واقعی به همراه دارد.
زبان Rust فلسفه متفاوتی را انتخاب کرده است؛ مفهومی به نام استثنا در این زبان وجود ندارد. به جای پنهان کردن خطرات، کامپایلر شما را وادار میکند همه مسیرهای احتمالی شکست یا فقدان مقدار را همان زمان نوشتن کد در نظر بگیرید. این هدف با دو ساختار داده بنیادی Option و Result تأمین میشود.
چرا Rust از سیستم استثنا دوری میکند
پرتاب استثنا مسیر اجرای کد را نامرئی تغییر میدهد. وقتی تابعی را صدا میزنید، مشخص نیست دقیقاً چه خطاهایی ممکن است از اعماق فراخوانیها به سمت بالا پرتاب شوند، مگر اینکه تکتک مستندات را بررسی کنید. علاوه بر این، ارجاع به مقادیر تهی یا همان null منشأ خطاهای پرهزینهای است که حافظه را به هم میریزند.
زبان Rust مفهوم مقدار تهی ندارد. اگر مقداری ممکن است وجود نداشته باشد، از نوع شمارشی Option استفاده میکنید که دو حالت دارد:
Some(T)یعنی مقدار معتبری از نوعTوجود دارد.Noneیعنی هیچ مقداری در دست نیست.
اگر عملیاتی با شکست روبهرو شود و نیاز باشد دلیل خطا را بررسی کنید، نوع Result به کار میآید. این شمارشی دو حالت مشخص دارد:
rustenum Result<T, E> {
Ok(T),
Err(E),
}
پارامتر T نوع مقدار در صورت موفقیت است و E ساختاری است که اطلاعات خطا را نگهداری میکند. تا زمانی که این دو حالت را بررسی نکنید، به داده داخل Ok دسترسی نخواهید داشت.
بررسی عملی باز کردن فایل با دستور match
وقتی تابعی مثل باز کردن فایل را اجرا میکنید، کامپایلر نوع بازگشتی را Result<File, io::Error> قرار میدهد. چون فایل ممکن است در مسیر خواسته شده نباشد یا مجوز خواندن آن را نداشته باشید. سادهترین راه برای تفکیک این دو حالت، به کار بردن عبارت match است.
rustuse std::fs::File;
use std::io::ErrorKind;
fn main() {
let file_result = File::open("config.txt");
let _file = match file_result {
// فایل پیدا شد و دسته دسترسی دریافت شد
Ok(handle) => handle,
// فایل باز نشد و خطا بررسی میشود
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("config.txt") {
Ok(created) => created,
Err(e) => panic!("خطا در ایجاد فایل: {e:?}"),
},
other_error => {
panic!("خطای دیگر در باز کردن فایل: {other_error:?}");
}
},
};
}
اگر فایل وجود نداشته باشد، خروجی به شکل کنترلشده بررسی میشود و برنامه به جای کرش ناگهانی، اقدام به ساخت فایل میکند. اگر هم سیستم دسترسی اجازه ساخت را ندهد، ماکروی panic با پیام دقیق فعال میشود.
جایگزینهای خواناتر به جای نوشتن matchهای تو در تو
نوشتن عبارتهای طولانی match گاهی بدنه کد را شلوغ میکند. کتابخانه استاندارد توابع کمکی کاربردی مانند unwrap_or_else را برای حل این مسئله تدارک دیده است:
rustuse std::fs::File;
use std::io::ErrorKind;
fn main() {
let _file = File::open("config.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("config.txt").unwrap_or_else(|e| {
panic!("امکان ساخت فایل وجود نداشت: {e:?}");
})
} else {
panic!("خطای سیستمی: {error:?}");
}
});
}
این فرمول ساختار تمیزتری میسازد و دقیقاً همان رفتار منطقی نمونه قبلی را حفظ میکند.
خطای رایج برنامهنویسان در استفاده از unwrap
بسیاری از افراد هنگام شروع کار با این زبان، پشت سر هر متد بازگرداننده خطا از unwrap() یا expect() استفاده میکنند. متد unwrap میگوید اگر مقدار Ok بود آن را بیرون بکش، و اگر Err بود بلافاصله برنامه را با خطا متوقف کن.
استفاده افراطی از این متد در کدهای واقعی، مزیت اصلی سیستم خطای این زبان را بیاثر میکند. متد unwrap تنها برای اسکریپتهای آزمایشی، نمونههای اولیه کوتاه یا مواردی مناسب است که از نظر منطق ریاضی مطمئن هستید خطا هرگز رخ نمیدهد.
قدم بعدی برای تسلط بر خطاهای Rust
گام بعدی در مسیر یادگیری، آشنایی با عملگر علامت سؤال (?) است که اجازه میدهد خطای توابع را به تابع بالادستی پاس بدهید بدون اینکه نیاز به زنجیرهای از شرطها داشته باشید. برای پیادهسازی گامبهگام این مفاهیم همراه با پروژههای عملی، میتوانید سرفصلهای دوره راست از صفر تا برنامهنویس را بررسی کنید.




نظرها
اولین نفری باشید که نظر میدهدسؤالتان را بپرسید یا تجربهتان را بنویسید تا نویسندهی مطلب پاسخ بدهد.