Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Невідновлювані помилки з 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.