Rust
Обработка ошибок
Поиск…
Вступление
Result<T, E>
чтобы указать восстановимые ошибки во время выполнения. Неустранимые ошибки вызывают панику, которая является отдельной темой.
замечания
Подробности обработки ошибок описаны на языке программирования ржавчины (aka the Book)
Общие методы результата
use std::io::{Read, Result as IoResult};
use std::fs::File;
struct Config(u8);
fn read_config() -> IoResult<String> {
let mut s = String::new();
let mut file = File::open(&get_local_config_path())
// or_else closure is invoked if Result is Err.
.or_else(|_| File::open(&get_global_config_path()))?;
// Note: In `or_else`, the closure should return a Result with a matching
// Ok type, whereas in `and_then`, the returned Result should have a
// matching Err type.
let _ = file.read_to_string(&mut s)?;
Ok(s)
}
struct ParseError;
fn parse_config(conf_str: String) -> Result<Config, ParseError> {
// Parse the config string...
if conf_str.starts_with("bananas") {
Err(ParseError)
} else {
Ok(Config(42))
}
}
fn run() -> Result<(), String> {
// Note: The error type of this function is String. We use map_err below to
// make the error values into String type
let conf_str = read_config()
.map_err(|e| format!("Failed to read config file: {}", e))?;
// Note: Instead of using `?` above, we can use `and_then` to wrap the let
// expression below.
let conf_val = parse_config(conf_str)
.map(|Config(v)| v / 2) // map can be used to map just the Ok value
.map_err(|_| "Failed to parse the config string!".to_string())?;
// Run...
Ok(())
}
fn main() {
match run() {
Ok(_) => println!("Bye!"),
Err(e) => println!("Error: {}", e),
}
}
fn get_local_config_path() -> String {
let user_config_prefix = "/home/user/.config";
// code to get the user config directory
format!("{}/my_app.rc", user_config_prefix)
}
fn get_global_config_path() -> String {
let global_config_prefix = "/etc";
// code to get the global config directory
format!("{}/my_app.rc", global_config_prefix)
}
Если конфигурационные файлы не существуют, это выводит:
Error: Failed to read config file: No such file or directory (os error 2)
Если синтаксический анализ не завершился, это приведет к:
Error: Failed to parse the config string!
Примечание. По мере роста проекта будет громоздок обрабатывать ошибки с помощью этих основных методов ( docs ), не теряя информацию о происхождении и пути распространения ошибок. Кроме того, некорректная практика заключается в том, чтобы преждевременно преобразовывать ошибки в строки, чтобы обрабатывать несколько типов ошибок, как показано выше. Гораздо лучший способ - использовать error-chain
в ящике.
Пользовательские типы ошибок
use std::error::Error;
use std::fmt;
use std::convert::From;
use std::io::Error as IoError;
use std::str::Utf8Error;
#[derive(Debug)] // Allow the use of "{:?}" format specifier
enum CustomError {
Io(IoError),
Utf8(Utf8Error),
Other,
}
// Allow the use of "{}" format specifier
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CustomError::Io(ref cause) => write!(f, "I/O Error: {}", cause),
CustomError::Utf8(ref cause) => write!(f, "UTF-8 Error: {}", cause),
CustomError::Other => write!(f, "Unknown error!"),
}
}
}
// Allow this type to be treated like an error
impl Error for CustomError {
fn description(&self) -> &str {
match *self {
CustomError::Io(ref cause) => cause.description(),
CustomError::Utf8(ref cause) => cause.description(),
CustomError::Other => "Unknown error!",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
CustomError::Io(ref cause) => Some(cause),
CustomError::Utf8(ref cause) => Some(cause),
CustomError::Other => None,
}
}
}
// Support converting system errors into our custom error.
// This trait is used in `try!`.
impl From<IoError> for CustomError {
fn from(cause: IoError) -> CustomError {
CustomError::Io(cause)
}
}
impl From<Utf8Error> for CustomError {
fn from(cause: Utf8Error) -> CustomError {
CustomError::Utf8(cause)
}
}
Итерация по причинам
Для целей отладки часто бывает полезно найти основную причину ошибки. Чтобы проверить значение ошибки, реализующее std::error::Error
:
use std::error::Error;
let orig_error = call_returning_error();
// Use an Option<&Error>. This is the return type of Error.cause().
let mut err = Some(&orig_error as &Error);
// Print each error's cause until the cause is None.
while let Some(e) = err {
println!("{}", e);
err = e.cause();
}
Основная отчетность об ошибках и обработка
Result<T, E>
- это тип enum
который имеет два варианта: Ok(T)
указывающий успешное выполнение со значимым результатом типа T
и Err(E)
указывающее на появление непредвиденной ошибки во время выполнения, описываемое значением типа E
,
enum DateError {
InvalidDay,
InvalidMonth,
}
struct Date {
day: u8,
month: u8,
year: i16,
}
fn validate(date: &Date) -> Result<(), DateError> {
if date.month < 1 || date.month > 12 {
Err(DateError::InvalidMonth)
} else if date.day < 1 || date.day > 31 {
Err(DateError::InvalidDay)
} else {
Ok(())
}
}
fn add_days(date: Date, days: i32) -> Result<Date, DateError> {
validate(&date)?; // notice `?` -- returns early on error
// the date logic ...
Ok(date)
}
Также см. Документы для более подробной информации ?
оператор.
Стандартная библиотека содержит Error
признак , который все типы ошибок рекомендуется реализовать. Ниже приведен пример реализации.
use std::error::Error;
use std::fmt;
#[derive(Debug)]
enum DateError {
InvalidDay(u8),
InvalidMonth(u8),
}
impl fmt::Display for DateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&DateError::InvalidDay(day) => write!(f, "Day {} is outside range!", day),
&DateError::InvalidMonth(month) => write!(f, "Month {} is outside range!", month),
}
}
}
impl Error for DateError {
fn description(&self) -> &str {
match self {
&DateError::InvalidDay(_) => "Day is outside range!",
&DateError::InvalidMonth(_) => "Month is outside range!",
}
}
// cause method returns None by default
}
Примечание. Как правило, параметр Option<T>
не должен использоваться для сообщения об ошибках. Option<T>
указывает на ожидаемую возможность отсутствия значения и единственную прямую причину этого. Напротив, Result<T, E>
используется для сообщения о неожиданных ошибках во время выполнения, и особенно когда существует несколько способов сбоев различения между ними. Кроме того, Result<T, E>
используется только как возвращаемые значения. ( Старая дискуссия. )