Невідновлювані помилки з panic!
Іноді у вашому коді трапляються погані речі, і ви нічого не можете з цим
зробити. У цих випадках у Rust є макрос panic!. У практиці є два способи
спричинити паніку: вчинити дію, яка спричиняє паніку нашого коду (наприклад,
звернення до масиву за його межами) або явно викликати макрос panic!.
В обох випадках ми спричиняємо паніку в нашій програмі. За замовчуванням ці
паніки друкуватимуть повідомлення про збій, розгортатимуться, очищатимуть
стек і завершуватимуть роботу. Через змінну середовища ви також можете
змусити Rust показувати зворотне трасування (backtrace) стека викликів, коли трапляється паніка, щоб легше
було відстежити джерело паніки.
Розгортання стека або аварійне завершення у відповідь на паніку
За замовчуванням, коли трапляється паніка, програма починає розгортання, що означає, що Rust рухається назад угору по стеку й очищає дані з кожної функції, яку зустрічає. Однак рух назад і очищення — це багато роботи. Тому Rust дає вам змогу вибрати альтернативу негайного аварійного завершення, яке завершує програму без очищення.
Пам’ять, яку програма використовувала, тоді потрібно буде очистити операційній системі. Якщо у вашому проєкті вам потрібно зробити отриманий двійковий файл якомога меншим, ви можете перемкнутися з розгортання на аварійне завершення під час паніки, додавши
panic = 'abort'до відповідних розділів[profile]у вашому файлі Cargo.toml. Наприклад, якщо ви хочете аварійно завершуватися під час паніки в фінальному (release) режимі, додайте це:[profile.release] panic = 'abort'
Спробуймо викликати panic! у простій програмі:
fn main() {
panic!("crash and burn");
}
Коли ви запустите програму, ви побачите щось подібне до цього:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Виклик panic! спричиняє повідомлення про помилку, що міститься в останніх
двох рядках. Перший рядок показує наше повідомлення про паніку і місце у нашому
вихідному коді, де сталася паніка: src/main.rs:2:5 означає, що це другий
рядок, п’ятий символ нашого файлу src/main.rs.
У цьому випадку вказаний рядок є частиною нашого коду, і якщо ми перейдемо до
цього рядка, то побачимо виклик макроса panic!. В інших випадках виклик
panic! може бути в коді, який викликає наш код, і назва файлу та номер рядка,
зазначені в повідомленні про помилку, будуть кодом когось іншого, де
викликається макрос panic!, а не рядком нашого коду, який зрештою призвів
до виклику panic!.
Ми можемо використати зворотне трасування (backtrace) функцій, з яких було викликано panic!, щоб
з’ясувати ту частину нашого коду, яка спричиняє проблему. Щоб зрозуміти, як
використовувати зворотне трасування (backtrace) panic!, давайте подивимося на ще один приклад і
побачимо, як це виглядає, коли виклик panic! надходить з бібліотеки через
помилку в нашому коді, а не від прямого виклику макроса нашим кодом. Лістинг 9-1 містить код, який намагається звернутися до індексу в векторі за межами
діапазону допустимих індексів.
fn main() {
let v = vec![1, 2, 3];
v[99];
}
Тут ми намагаємося отримати доступ до 100-го елемента нашого вектора (який
знаходиться за індексом 99, оскільки індексація починається з нуля), але
вектор має лише три елементи. У цій ситуації Rust панікуватиме. Використання
[] має повертати елемент, але якщо ви передаєте недійсний індекс, тут немає
елемента, який Rust міг би повернути і який був би правильним.
У C спроба прочитати за кінцем структури даних є невизначеною поведінкою (undefined behavior). Ви можете отримати те, що знаходиться в розташуванні в пам’яті, яке відповідало б цьому елементу в структурі даних, навіть якщо ця пам’ять не належить цій структурі. Це називається читанням за межами буфера (buffer overread) і може призвести до вразливостей безпеки, якщо зловмисник може маніпулювати індексом так, щоб прочитати дані, до яких йому не слід мати доступу, і які зберігаються після структури даних.
Щоб захистити вашу програму від такого роду вразливості, якщо ви спробуєте прочитати елемент за неіснуючим індексом, Rust зупинить виконання і відмовиться продовжувати. Спробуймо це і подивімося:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Ця помилка вказує на рядок 4 нашого main.rs, де ми намагаємося отримати
доступ до індексу 99 вектора в v.
Рядок note: повідомляє нам, що ми можемо встановити змінну середовища
RUST_BACKTRACE, щоб отримати зворотне трасування (backtrace) того, що саме сталося і спричинило
помилку. Зворотне трасування (backtrace) — це список усіх функцій, які було викликано, щоб
дійти до цього моменту. Зворотне трасування в Rust працює так само, як і в інших
мовах: ключ до читання зворотного трасування — почати зверху й читати, доки ви не побачите
файли, які написали ви. Це місце, де виникла проблема. Рядки вище цього місця —
це код, який викликав ваш код; рядки нижче — це код, який викликав ваш код. Ці
рядки до і після можуть включати код core Rust, код стандартної бібліотеки або
крейти, які ви використовуєте. Спробуймо отримати зворотне трасування, встановивши змінну
середовища RUST_BACKTRACE у будь-яке значення, крім 0. Лістинг 9-2 показує
вивід, подібний до того, який ви побачите.
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
0: rust_begin_unwind
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
1: core::panicking::panic_fmt
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
2: core::panicking::panic_bounds_check
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:273:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:274:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:16:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3361:9
6: panic::main
at ./src/main.rs:4:6
7: core::ops::function::FnOnce::call_once
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Це багато виводу! Точний вивід, який ви бачите, може відрізнятися залежно від
вашої операційної системи та версії Rust. Щоб отримувати зворотне трасування (backtrace) з цією
інформацією, мають бути увімкнені символи налагодження. Символи налагодження
вмикаються за замовчуванням під час використання cargo build або cargo run
без прапорця --release, як ми робимо тут.
У виводі у Лістингу 9-2 рядок 6 зворотного трасування вказує на рядок у нашому проєкті, який спричиняє проблему: рядок 4 у src/main.rs. Якщо ми не хочемо, щоб наша програма панікувала, ми маємо почати наше розслідування з місця, на яке вказує перший рядок, що згадує файл, який ми написали. У Лістингу 9-1, де ми навмисно написали код, який панікуватиме, способом виправити паніку є не запитувати елемент за межами діапазону індексів вектора. Коли ваш код у майбутньому панікуватиме, вам потрібно буде з’ясувати, яку дію код виконує і з якими значеннями, щоб спричинити паніку, і що код має робити замість цього.
Ми повернемося до panic! і до того, коли ми повинні і коли не повинні
використовувати panic! для обробки умов помилок, у розділі “Панікувати чи не
панікувати” пізніше в цьому
розділі. Далі ми подивимося, як відновитися після помилки за допомогою Result.