Rust

روز ۲۷: Error Handling با Result در Rust

مدیریت خطا یکی از مهم‌ترین تفاوت‌های Rust با بسیاری از زبان‌های برنامه‌ نویسی رایج است و دلیل اصلی شهرت Rust به عنوان زبانی ایمن و قابل اعتماد محسوب می‌شود. در Rust برخلاف زبان‌هایی که به استثناها یا try-catch متکی هستند، مدیریت خطا به شکل کاملاً صریح و قابل پیش‌بینی انجام می‌شود. این رویکرد باعث می‌شود برنامه‌نویس از همان ابتدا مجبور به فکر کردن درباره حالت‌های خطا باشد و از نادیده گرفتن آن‌ها جلوگیری شود. در قلب این سیستم، نوع داده‌ای به نام Result قرار دارد که پایه و اساس Error Handling در Rust را تشکیل می‌دهد. یادگیری صحیح Result نقطه عطفی در مسیر حرفه‌ای شدن با Rust است.

Result چیست و چرا در Rust این‌قدر مهم است؟

Result یک enum جنریک در Rust است که برای نمایش نتیجه یک عملیات استفاده می‌شود؛ عملیاتی که ممکن است موفق باشد یا با خطا مواجه شود. این نوع داده دو حالت دارد: Ok که نشان‌دهنده موفقیت عملیات است و Err که بیانگر وقوع خطا می‌باشد. نکته کلیدی اینجاست که Rust شما را مجبور می‌کند هر دو حالت را در نظر بگیرید و این موضوع از بروز خطاهای پنهان در زمان اجرا جلوگیری می‌کند. برخلاف زبان‌هایی که خطاها را تا لحظه اجرا مخفی نگه می‌دارند، Rust همه چیز را در زمان کامپایل شفاف می‌کند و همین مسئله باعث پایداری بالای برنامه‌ها می‌شود.

fn read_number(input: &str) -> Result<i32, String> {
    input.parse::<i32>().map_err(|_| "Invalid number".to_string())
}

در این مثال، تابع یا یک عدد صحیح معتبر برمی‌گرداند یا یک پیام خطا. همین امضای تابع به‌تنهایی به ما می‌گوید که امکان شکست وجود دارد و نباید آن را نادیده بگیریم.

استفاده از match برای مدیریت Result

ساده‌ترین و شفاف‌ترین روش برای کار با Result استفاده از match است. با match می‌توان دقیقاً مشخص کرد که در صورت موفقیت چه اتفاقی بیفتد و در صورت خطا چه واکنشی داشته باشیم. این روش خوانایی بالایی دارد و برای یادگیری اولیه Error Handling بسیار مناسب است. استفاده از match باعث می‌شود منطق مدیریت خطا کاملاً واضح و قابل دنبال کردن باشد، مخصوصاً در پروژه‌هایی که هنوز پیچیدگی زیادی ندارند.

let result = read_number("42");

match result {
    Ok(value) => println!("Parsed number: {}", value),
    Err(error) => println!("Error occurred: {}", error),
}

در اینجا هیچ حالت مبهمی وجود ندارد. یا مقدار صحیح داریم یا خطا، و هر دو مسیر کاملاً کنترل شده‌اند. این شفافیت یکی از فلسفه‌های اصلی Rust است.

اپراتور ? و ساده‌سازی Error Handling

وقتی کد شما بزرگ‌تر می‌شود، استفاده مداوم از match می‌تواند باعث شلوغی و کاهش خوانایی شود. Rust برای حل این مشکل اپراتور ? را معرفی کرده است. این اپراتور در صورت موفقیت مقدار داخل Ok را برمی‌گرداند و در صورت خطا، همان Err را به تابع فراخواننده منتقل می‌کند. استفاده درست از ? باعث می‌شود کد تمیزتر، کوتاه‌تر و حرفه‌ای‌تر به نظر برسد.

fn process_input(input: &str) -> Result<i32, String> {
    let number = read_number(input)?;
    Ok(number * 2)
}

در این مثال، اگر read_number با خطا مواجه شود، تابع process_input نیز همان خطا را برمی‌گرداند بدون نیاز به نوشتن match اضافی. این الگو در پروژه‌های واقعی Rust بسیار رایج است.

تفاوت unwrap و expect و چرا باید محتاط بود

دو متد unwrap و expect برای استخراج مقدار Ok از Result استفاده می‌شوند، اما اگر Result در حالت Err باشد، برنامه panic می‌کند. این رفتار برای کدهای آزمایشی یا prototype قابل قبول است، اما در کد production بسیار خطرناک محسوب می‌شود. تفاوت اصلی expect با unwrap در این است که expect اجازه می‌دهد پیام خطای سفارشی مشخص کنید، که در زمان دیباگ بسیار مفیدتر است.

let value = read_number("10").expect("Failed to parse number");

هرچند این کد ساده به نظر می‌رسد، اما استفاده بی‌رویه از unwrap و expect برخلاف فلسفه Rust است. Rust شما را تشویق می‌کند که خطاها را مدیریت کنید، نه اینکه از آن‌ها فرار کنید.

طراحی APIهای امن با Result

یکی از کاربردهای مهم Result در طراحی APIهای داخلی و عمومی است. وقتی توابع شما Result برمی‌گردانند، کاربران API دقیقاً می‌دانند که باید خطاها را مدیریت کنند. این موضوع باعث می‌شود کدهای وابسته پایدارتر و قابل پیش‌بینی‌تر باشند. در Rust، یک API خوب APIای است که امکان شکست را صادقانه اعلام کند و مصرف‌کننده را مجبور به تصمیم‌گیری آگاهانه نماید.

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

این تابع هیچ‌وقت برنامه را کرش نمی‌کند و کنترل کامل خطا را در اختیار caller قرار می‌دهد.

چرا Error Handling با Result یکی از نقاط قوت Rust است؟

سیستم Result باعث می‌شود خطاها بخشی از طراحی نرم‌افزار باشند نه اتفاقی ناخواسته. این رویکرد در پروژه‌های بزرگ، سیستم‌های concurrent و برنامه‌های حساس اهمیت حیاتی دارد. Rust با اجبار به مدیریت خطا در زمان کامپایل، هزینه‌های دیباگ در زمان اجرا را به شدت کاهش می‌دهد. همین ویژگی است که Rust را به گزینه‌ای محبوب برای سیستم‌های سطح پایین، بک‌اندهای پر بار و ابزارهای زیرساختی تبدیل کرده است.

جمع‌بندی Error Handling در Rust

در این روز یاد گرفتیم که Result چگونه ستون فقرات مدیریت خطا در Rust است و چرا این زبان مسیر متفاوتی نسبت به exceptionها انتخاب کرده است. استفاده از match، اپراتور ? و پرهیز آگاهانه از unwrap و expect، همگی مهارت‌هایی هستند که شما را یک قدم به Rustacean حرفه‌ای شدن نزدیک‌تر می‌کنند. اگر Error Handling را به‌درستی درک کنید، بخش بزرگی از فلسفه Rust را فهمیده‌اید و آماده ورود به مباحث پیشرفته‌تر خواهید بود.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

5 × یک =

دکمه بازگشت به بالا