Обработка ошибок
В Rust ошибки группируются на две основные категории: исправимые (recoverable) и неисправимые (unrecoverable).
В Rust нет исключений. Вместо этого он имеет тип Result<T, E> для обрабатываемых (исправимых) ошибок и макрос panic!, который останавливает выполнение, когда программа встречает необрабатываемую (неисправимую) ошибку.
Когда происходит паника, программа начинает процесс раскрутки стека, означающий в Rust проход обратно по стеку вызовов и очистку данных для каждой обнаруженной функции.
Warning
Если в вашем проекте нужно насколько это возможно сделать маленьким исполняемый файл, вы можете переключиться с варианта раскрутки стека на вариант прерывания при панике, добавьте panic = 'abort' в раздел
[profile]вашего Cargo.toml файла.[profile.release] panic = 'abort'
При добавление в код макрос panic!
Программа прервется
Используем обратную трассировку
~/data/rs/panic [101] $ RUST_BACKTRACE=1 cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:5:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panicking.rs:697:5
1: core::panicking::panic_fmt
at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/panicking.rs:75:14
2: core::panicking::panic_bounds_check
at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/panicking.rs:280:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /home/edge/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/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 /home/edge/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:18:15
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at /home/edge/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3571:9
6: panic::main
at ./src/main.rs:5:6
7: core::ops::function::FnOnce::call_once
at /home/edge/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:253:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
~/data/rs/panic [101] $
Исправление ошибок с помощью Result
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
Tпредставляет тип значения, которое будет возвращено в случае успеха внутри вариантаOk, аEпредставляет тип ошибки, которая будет возвращена при сбое внутри вариантаErr.
use std::{fs::File, io::ErrorKind}; fn main() { let greeting_file_result = File::open("hello.txt"); let _greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Какая-то проблема: {e:?}"), }, _ => { panic!("Проблема открытия файла: {error:?}"); } }, }; }
Но есть еще вариант решить эту проблему через замыкания
use std::fs::File; use std::io::ErrorKind; fn main() { let greeting_file = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
Обработка ошибок с помощью unwrap и expect
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt").unwrap(); }
unwrap выполняет туже работу что match:
- возвращает результат OK или
- вызывает panic! с описанием ошибки
expect позволяет указать сообщение для макроса panic!
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt") .expect("hello.txt should be included in this project"); }
Проброс ошибок
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let username_file_result = File::open("hello.txt"); let mut username_file = match username_file_result { Ok(file) => file, Err(e) => return Err(e), }; let mut username = String::new(); match username_file.read_to_string(&mut username) { Ok(_) => Ok(username), Err(e) => Err(e), } } }
Оператор ? для проброса ошибок
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) } }
определим OurError. Если мы также определим
impl From<io::Error> for OurErrorдля создания экземпляра OurError из io::Error, то оператор ?, вызываемый в теле read_username_from_file, вызовет from и преобразует типы ошибок без необходимости добавления дополнительного кода в функцию.
Как итог:
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username = String::new(); File::open("hello.txt")?.read_to_string(&mut username)?; Ok(username) } }
Или записать весь файл в строку
#![allow(unused)] fn main() { use std::fs; use std::io; fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") } }
?можно использовать и со значениямиOption<T>
#![allow(unused)] fn main() { fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() } }
main тоже может возвращать Result
use std::error::Error; use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let greeting_file = File::open("hello.txt")?; Ok(()) }
panic! или не panic!
При панике код не имеет возможности восстановить своё выполнение.
В ситуациях как примеры, прототипы и тесты, более уместно писать код, который паникует вместо возвращения Result.
Когда код должен паниковать:
- Некорректное состояние — это что-то неожиданное, отличается от того, что может происходить время от времени, например, когда пользователь вводит данные в неправильном формате.
- Ваш код после этой точки должен полагаться на то, что он не находится в некорректном состоянии, вместо проверок наличия проблемы на каждом этапе.
- Нет хорошего способа закодировать данную информацию в типах, которые вы используете.
Создание своего типа
#![allow(unused)] fn main() { pub struct Guess { value: i32, } impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { panic!("Guess value must be between 1 and 100, got {value}."); } Guess { value } } pub fn value(&self) -> i32 { self.value } } }