English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Rust имеет уникальный механизм обработки исключительных ситуаций, который не так прост, как механизм try в других языках.
Сначала в программе обычно встречаются два типа ошибок: восстанавливаемые ошибки и невозстанавливаемые ошибки.
Примером восстанавливаемой ошибки является ошибка доступа к файлу, если доступ к файлу не удается, возможно, он занят, это нормально, мы можем решить это, подождав.
Но есть еще ошибки, которые вызваны логическими ошибками, которые не могут быть решены в программировании, например, доступ к положению за концом массива.
Большинство языков программирования не различают эти два типа ошибок и используют класс Exception (исключение) для их представления. В Rust нет Exception.
Для восстанавливаемых ошибок используется класс Result<T, E>, для невозстанавливаемых ошибок используется макрос panic!.
В этой главе не было专门介绍а синтаксиса макросов Rust, но мы уже использовали макрос println!,因为这些 макросы легко использовать, поэтому пока не нужно полностью освоить их, мы можем сначала научиться использовать макрос panic!.
fn main() { panic!("error occurred"); println!("Hello, Rust"); }
Результат выполнения:
thread 'main' panicked at 'error occurred', src\main.rs:3:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Очевидно, что программа не может работать до println!("Hello, Rust"), а останавливается при вызове макроса panic!.
Невосстанавливаемые ошибки обязательно приведут к тому, что программа получит смертельный удар и прекратит работу.
Давайте обратим внимание на две строки вывода ошибки:
В первой строке выводится положение макроса panic! и его выводимая ошибка.
Второй строке соответствует подсказка, переведенная на русский язык, гласит: "Запустите через переменную окружения `RUST_BACKTRACE=1`, чтобы отобразить отладочную информацию". Далее мы рассмотрим отладочную информацию (backtrace).
Следуя предыдущему примеру, создадим новый терминал в VSCode:
В новом терминале установите переменные окружения (методы установки различаются в зависимости от терминала, здесь мы рассмотрим два основных метода):
Если вы используете Windows 7 и более новые версии Windows, по умолчанию используется командная строка Powershell, пожалуйста, используйте следующую команду:
$env:RUST_BACKTRACE=1 ; cargo run
Если вы используете Linux или macOS и т.д. системы UNIX, в большинстве случаев по умолчанию используется командная строка bash, пожалуйста, используйте следующую команду:
RUST_BACKTRACE=1 cargo run
Затем, вы увидите следующий текст:
thread 'main' panicked at 'error occurred', src\main.rs:3:5 stack backtrace: ... 11: greeting::main at .\src\main.rs:3 ...
Отладка по стека является еще одним способом обработки невозместимых ошибок, который разворачивает运行的 стек и выводит все сообщения, после чего программа все равно завершается. Пропущены的大量 выводных сообщений, и мы можем найти ошибки, вызванные макросом panic!:
Эта концепция очень похожа на исключения в языках программирования Java. На самом деле в языке C мы часто используем возвращаемые значения функций в виде целых чисел для выражения ошибок, в Rust мы используем возвращаемые значения枚нума Result<T, E> для выражения исключений:
enum Result<T, E> { Ok(T), Err(E), }
В стандартной библиотеке Rust функции, которые могут вызывать исключения, возвращают значения типа Result. Например: когда мы пытаемся открыть файл:
use std::fs::File; fn main() { let f = File::open("hello.txt"); match f { Ok(file) => { println!("Файл успешно открыт."); }, Err(err) => { println!("Не удалось открыть файл."); } } }
Если файл hello.txt не существует, будет напечатано: "Не удалось открыть файл."。
Конечно, синтаксис if let, о котором мы говорили в разделе о перечислениях, может упростить блоки синтаксиса match:
use std::fs::File; fn main() { let f = File::open("hello.txt"); if let Ok(file) = f { println!("Файл успешно открыт."); } println!("Не удалось открыть файл."); } }
Если нужно обработать ошибку, которая может быть восстановлена, как невозместимую ошибку, класс Result предоставляет два способа: unwrap() и expect(message: &str):
use std::fs::File; fn main() { let f1 = File::open("hello.txt").unwrap(); let f2 = File::open("hello.txt").expect("Failed to open."); }
Этот фрагмент программы эквивалентен вызову макроса panic! когда Result является Err. Разница в том, что expect может отправить макросу panic! строку с указанным сообщением об ошибке.
Ранее мы говорили о способах обработки полученных ошибок, но что если мы хотим передавать ошибки, когда我们自己 пишем функцию и встречаем ошибку?
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn main() { let r = f(10000); if let Ok(v) = r { println!("Ok: f(-1) = {}", v); } println!("Err"); } }
Результат выполнения:
Ok: f(-1) = 10000
В этом фрагменте программы функция f является источником ошибки, теперь давайте напишем функцию g, передающую ошибки:
fn g(i: i32) -> Result<i32, bool> { let t = f(i); return match t { Ok(i) => Ok(i), Err(b) => Err(b) } }
Функция g передает ошибки, которые может вызвать функция f (здесь g - это только пример, на самом деле функция, передающая ошибки, может включать много других операций).
Такой способ написания может быть излишне длинным, в Rust можно добавить оператор ? к объекту Result, чтобы напрямую передавать аналогичные Err:
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn g(i: i32) -> Result<i32, bool> { let t = f(i)?; Ok(t) // потому что t определенно не Err, t здесь уже тип i32 } fn main() { let r = g(10000); if let Ok(v) = r { println!("Ok: g(10000) = {}", v); } println!("Err"); } }
Результат выполнения:
Ok: g(10000) = 10000
? знак фактически извлекает значение Result класса без исключений, а если есть исключение, то возвращает Result с исключением. Поэтому, ? знакиспользуется только для функций с типом возвращаемого значения Result<T, E>, где тип E должен соответствовать типу E, обрабатываемому ?.
До этого Rust,似乎 не имеет синтаксиса try-блока, который позволяет решать все типы исключений на одном уровне, но это не означает, что Rust не может это сделать: мы完全可以 реализовать try-блок в отдельной функции и передать все исключения для решения.На самом деле, это и есть правильный подход к программированию: следует уделять внимание целостности независимых функций.
Но для этого нужно�断 тип Result Err, получить функцию типа Err - это kind().
use std::io; use std::io::Read; use std::fs::File; fn read_text_from_file(path: &str) -> Result<String, io::Error> { let mut f = File::open(path)?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } fn main() { let str_file = read_text_from_file("hello.txt"); match str_file { Ok(s) => println!("{}", s), Err(e) => { match e.kind() { io::ErrorKind::NotFound => { println!("No such file"); }, _ => { println!("Cannot read the file"); } } } } }
Результат выполнения:
Файл не найден