مقدمه

به فصل «مدیریت خطا» خوش آمدید. زبان Rust به دلیل تمرکز بر روی استحکام و قابلیت اطمینان، رویکرد بسیار دقیقی به مدیریت خطا دارد. در Rust، خطاها به دو دسته اصلی تقسیم می‌شوند: خطاهای «قابل بازیابی» (recoverable) و خطاهای «غیرقابل بازیابی» (unrecoverable).

  • خطاهای قابل بازیابی: این‌ها خطاهایی هستند که انتظار وقوع آنها می‌رود و برنامه باید بتواند به صورت منطقی به آنها واکنش نشان دهد. برای مثال، تلاش برای باز کردن فایلی که وجود ندارد. در این موارد، ما معمولاً می‌خواهیم خطا را به کد فراخواننده برگردانیم تا او تصمیم بگیرد چه کاری انجام دهد. در Rust، این نوع خطاها با استفاده از نوع Result<T, E> مدیریت می‌شوند.
  • خطاهای غیرقابل بازیابی: این‌ها خطاهایی هستند که نشان‌دهنده یک باگ یا یک وضعیت غیرمنتظره و بحرانی در برنامه هستند که ادامه اجرای برنامه از آن نقطه به بعد، ایمن یا منطقی نیست. برای مثال، تلاش برای دسترسی به یک ایندکس خارج از محدوده در یک آرایه. در این موارد، Rust با استفاده از ماکروی panic! اجرای برنامه را متوقف می‌کند.

در این درس، ما بر روی دسته دوم، یعنی خطاهای غیرقابل بازیابی و مکانیزم panic تمرکز خواهیم کرد.

متوقف کردن اجرا با ماکروی panic!

وقتی ماکروی panic! اجرا می‌شود، برنامه شما یک پیام خطا چاپ می‌کند، حافظه stack را پاک‌سازی می‌کند (unwinding) و سپس از برنامه خارج می‌شود. این ساده‌ترین راه برای مدیریت یک وضعیت خطای بحرانی است.

شما می‌توانید به صورت دستی و در هر جای کدتان، panic! را فراخوانی کنید.

Copy Icon src/main.rs
fn main() {
    panic!("Farewell, cruel world!");
}

اجرای این کد، خروجی مشابه زیر را تولید خواهد کرد:

$ cargo run
   Compiling panic v0.1.0 (...)
    Finished dev [unoptimized + debuginfo] target(s) in ...s
     Running `target/debug/panic`
thread 'main' panicked at 'Farewell, cruel world!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
                    

همانطور که می‌بینید، پیام پنیک شامل پیامی است که ما ارائه کرده‌ایم و همچنین مکان دقیق وقوع آن در کد منبع.

ردیابی منشأ خطا با Backtrace

پیام خطای panic! به ما می‌گوید که در کدام خط از کد مشکل رخ داده است، اما همیشه نمی‌گوید چرا. برای فهمیدن این موضوع، ما نیاز به یک backtrace داریم. backtrace لیستی از تمام توابعی است که تا لحظه وقوع خطا فراخوانی شده‌اند. این لیست به ما کمک می‌کند تا مسیر اجرای کد را ردیابی کرده و منشأ باگ را پیدا کنیم.

همانطور که در پیام خطای قبلی اشاره شده، برای دریافت backtrace، باید متغیر محیطی RUST_BACKTRACE را برابر با `1` قرار داده و سپس برنامه را اجرا کنیم. بیایید یک مثال پیچیده‌تر را بررسی کنیم که در آن، panic به صورت غیرمستقیم و توسط کد کتابخانه استاندارد رخ می‌دهد.

Copy Icon src/main.rs
fn main() {
    let v = vec![1, 2, 3];

    v[99]; // Accessing an index out of bounds will cause a panic
}

اجرای این کد با cargo run منجر به یک پنیک می‌شود. حالا بیایید آن را با فعال کردن backtrace اجرا کنیم:

$ RUST_BACKTRACE=1 cargo run
                    

خروجی شما بسته به سیستم عامل و نسخه Rust ممکن است کمی متفاوت باشد، اما ساختار کلی آن شبیه به این خواهد بود:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/b.../library/std/src/panicking.rs:597:5
   1: core::panicking::panic_fmt
             at /rustc/b.../library/core/src/panicking.rs:64:14
   2: core::panicking::panic_bounds_check
             at /rustc/b.../library/core/src/panicking.rs:102:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /rustc/b.../library/core/src/slice/index.rs:242:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /rustc/b.../library/core/src/slice/index.rs:18:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at /rustc/b.../library/alloc/src/vec/mod.rs:2737:9
   6: panic::main
             at ./src/main.rs:4:5
   ... (more lines)
                    

با خواندن این backtrace از پایین به بالا، می‌توانیم مسیر اجرای کد را دنبال کنیم. خط شماره ۶ به ما می‌گوید که پنیک از خط ۴ فایل src/main.rs ما شروع شده است. خطوط بالاتر، توابع داخلی Rust را نشان می‌دهند که توسط v[99] فراخوانی شده‌اند و در نهایت منجر به تشخیص خطای "index out of bounds" شده‌اند. این اطلاعات برای دیباگ کردن بسیار ارزشمند است.

در این درس با مکانیزم panic! به عنوان روش Rust برای مدیریت خطاهای غیرقابل بازیابی آشنا شدیم. دیدیم که این خطاها معمولاً نشان‌دهنده یک باگ در برنامه هستند و چگونه می‌توان با استفاده از backtrace منشأ آنها را ردیابی کرد. در درس بعدی، به سراغ «خطاهای قابل بازیابی» خواهیم رفت و با نوع Result<T, E> به عنوان روش اصولی و قدرتمند Rust برای مدیریت خطاهایی که انتظار وقوع آنها می‌رود، آشنا خواهیم شد.