Неисправимые ошибки с использованием panic

RustRustBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Добро пожаловать в Unrecoverable Errors With Panic. Этот лаба является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.

В этом лабе мы узнаем о паниках в Rust и том, как они могут быть вызваны действиями в нашем коде или явным вызовом макроса panic!, а также о том, как использовать трассировки стека, чтобы определить источник паники.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/ErrorHandlingandDebuggingGroup(["Error Handling and Debugging"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/ErrorHandlingandDebuggingGroup -.-> rust/panic_usage("panic! Usage") subgraph Lab Skills rust/variable_declarations -.-> lab-100409{{"Неисправимые ошибки с использованием panic"}} rust/function_syntax -.-> lab-100409{{"Неисправимые ошибки с использованием panic"}} rust/expressions_statements -.-> lab-100409{{"Неисправимые ошибки с использованием panic"}} rust/panic_usage -.-> lab-100409{{"Неисправимые ошибки с использованием panic"}} end

Неисправимые ошибки с использованием panic

Иногда в вашем коде случаются плохие вещи, и вы对此 ничего не можете сделать. В таких случаях Rust имеет макрос panic!. В практике есть два способа вызвать панику: выполнив действие, которое приводит к панике в нашем коде (например, доступ к массиву за его пределы) или явно вызвав макрос panic!. В обоих случаях мы вызываем панику в нашем программе. По умолчанию эти паники выводят сообщение об ошибке, разматывают стек, очищают его и завершают работу. Через переменную окружения вы также можете заставить Rust отображать стек вызовов при возникновении паники, чтобы легче определить источник паники.

Разматывание стека или аборт при панике

По умолчанию при возникновении паники программа начинает разматываться, что означает, что Rust идёт обратно по стеку и очищает данные из каждой функции, которую он встречает. Однако разматывание и очистка занимают много работы. Поэтому Rust позволяет вам выбрать альтернативу — немедленно аботировать, что завершает программу без очистки.

Затем память, которую использовала программа, должна быть очищена операционной системой. Если в вашем проекте нужно сделать результирующий бинарник как можно меньше, вы можете переключиться с разматывания на аборт при панике, добавив panic = 'abort' в соответствующие разделы [profile] в вашем файле Cargo.toml. Например, если вы хотите аботить при панике в режиме выпуска, добавьте это:

[profile.release]
panic = 'abort'

Попробуем вызвать panic! в простой программе:

Имя файла: src/main.rs

fn main() {
    panic!("crash and burn");
}

Когда вы запустите программу, вы увидите что-то вроде этого:

thread 'main' panicked at 'crash and burn', src/main.rs:2:5
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!.

Мы можем использовать стек вызовов функций, из которых произошёл вызов panic!, чтобы определить часть нашего кода, которая вызывает проблему. Чтобы понять, как использовать стек вызовов при panic!, давайте рассмотрим другой пример и посмотрим, как выглядит ситуация, когда вызов panic! происходит из библиотеки из-за ошибки в нашем коде, а не из нашего кода, который напрямую вызывает макрос. В листинге 9-1 есть код, который пытается получить доступ к индексу в векторе за пределами диапазона допустимых индексов.

Имя файла: src/main.rs

fn main() {
    let v = vec![1, 2, 3];

    v[99];
}

Листинг 9-1: Попытка доступа к элементу за пределами вектора, что вызовет panic!

Здесь мы пытаемся получить доступ к 100-му элементу нашего вектора (который находится по индексу 99, так как индексация начинается с нуля), но вектор имеет только три элемента. В такой ситуации Rust будет паниковать. Использование [] должно возвращать элемент, но если вы передадите недопустимый индекс, здесь нет элемента, который Rust мог бы вернуть правильно.

В C попытка прочитать данные за пределы структуры данных является неопределённым поведением. Возможно, вы получите то, что находится в памяти в месте, соответствующем этому элементу в структуре данных, даже если память не принадлежит этой структуре. Это называется переходом за пределы буфера и может привести к уязвимостям безопасности, если злоумышленник сможет манипулировать индексом таким образом, чтобы прочитать данные, к которым он не имеет доступ, хранящиеся после структуры данных.

Чтобы защитить вашу программу от такого рода уязвимостей, если вы пытаетесь прочитать элемент по индексу, который не существует, Rust остановит выполнение и откажется продолжать. Давайте попробуем и посмотрим:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Это сообщение об ошибке указывает на строку 4 нашего main.rs, где мы пытаемся получить доступ к index.

Строка note: сообщает нам, что мы можем установить переменную окружения RUST_BACKTRACE, чтобы получить стек вызовов того, что именно произошло, чтобы вызвать ошибку. Стек вызовов — это список всех функций, которые были вызваны, чтобы прийти к этой точке. Стек вызовов в Rust работает так же, как и в других языках: ключ к чтению стека вызовов — начать с верхней строки и читать до тех пор, пока не увидите файлы, которые вы написали. Именно в этом месте возникла проблема. Строки выше этой точки — это код, который вызывается вашим кодом; строки ниже — это код, который вызывает ваш код. Эти строки до и после могут включать в себя код ядра Rust, стандартную библиотеку или крейты, которые вы используете. Давайте попробуем получить стек вызовов, установив переменную окружения RUST_BACKTRACE любым значением, кроме 0. Листинг 9-2 показывает вывод, похожий на то, что вы увидите.

$ RUST_BACKTRACE=1 cargo run
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/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std
/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core
/src/panicking.rs:142:14
2: core::panicking::panic_bounds_check
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core
/src/panicking.rs:84:5
3: < usize as core::slice::index::SliceIndex < [T] >> ::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core
/src/slice/index.rs:242:10
4: core::slice::index:: core::ops::index::Index [T] < impl < I > for > ::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core
/src/slice/index.rs:18:9
5: < alloc::vec::Vec < T,A > as core::ops::index::Index < I >> ::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc
/src/vec/mod.rs:2591:9
6: panic::main
at./src/main.rs:4:5
7: core::ops::function::FnOnce::call_once
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core
/src/ops/function.rs:248:5
note: Some details are omitted, run with $(RUST_BACKTRACE=full) for a verbose
backtrace.

Листинг 9-2: Стек вызовов, сгенерированный вызовом panic!, отображаемый при установке переменной окружения RUST_BACKTRACE

Это много вывода! Точный вывод, который вы увидите, может отличаться в зависимости от вашей операционной системы и версии Rust. Чтобы получить стек вызовов с этой информацией, отладочные символы должны быть включены. Отладочные символы включены по умолчанию при использовании cargo build или cargo run без флага --release, как мы здесь имеем.

В выводе в листинге 9-2 шестая строка стека вызовов указывает на строку в нашем проекте, которая вызывает проблему: строку 4 файла src/main.rs. Если мы не хотим, чтобы наша программа паниковала, мы должны начать наше исследование в месте, указанном первой строкой, упоминающей файл, который мы написали. В листинге 9-1, где мы специально написали код, который должен вызвать панику, способ исправить панику — это не запрашивать элемент за пределами диапазона индексов вектора. Когда ваш код будет паниковать в будущем, вам нужно будет понять, какое действие выполняет код с какими значениями, чтобы вызвать панику, и что должен делать код вместо этого.

Мы вернёмся к panic! и тому, когда мы должны и не должны использовать panic! для обработки ошибочных ситуаций в разделе "Паниковать или не паниковать!". Далее мы рассмотрим, как восстанавливаться из ошибки с использованием Result.

Резюме

Поздравляем! Вы завершили лабораторную работу "Неисправимые ошибки с использованием panic". Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.