Правильная обработка ошибок Rust (автоматическое преобразование из одного типа ошибки в другое с вопросительным знаком)
Я хочу узнать, как правильно бороться с ошибками в Rust. Я прочитал книгу и этот пример; Теперь я хотел бы знать, как мне следует устранять ошибки в этой функции:
fn get_synch_point(&self) -> Result<pv::synch::MeasPeriods, reqwest::Error> {
let url = self.root.join("/term/pv/synch"); // self.root is url::Url
let url = match url {
Ok(url) => url,
// ** this err here is url::ParseError and can be converted to Error::Kind https://docs.rs/reqwest/0.8.3/src/reqwest/error.rs.html#54-57 **//
Err(err) => {
return Err(Error {
kind: ::std::convert::From::from(err),
url: url.ok(),
})
}
};
Ok(reqwest::get(url)?.json()?) //this return reqwest::Error or convert to pv::sych::MeasPeriods automaticly
}
этот код неправильный; это вызывает ошибку компиляции:
error[E0451]: field 'kind' of struct 'reqwest::Error' is private
--> src/main.rs:34:42
|
34 | Err(err) => return Err(Error{kind: ::std::convert::From::from(err), url: url.ok()})
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field 'kind' is private
error[E0451]: field 'url' of struct 'reqwest::Error' is private
--> src/main.rs:34:81
|
34 | Err(err) => return Err(Error{kind: ::std::convert::From::from(err), url: url.ok()})
| ^^^^^^^^^^^^^ field 'url' is private
Каков правильный порядок действий в этом случае? Для меня reqwest::Error
в данном случае является хорошим решением, поэтому я бы хотел избежать определения собственного типа ошибки:
enum MyError {
Request(reqwest::Error),
Url(url::ParseError) // this already a part of request::Error::Kind!!!
}
Ответы
Ответ 1
В этом случае повторное использование основного типа ошибки невозможно, поскольку вы не можете создать его скрытые поля. И даже когда это возможно, я бы советовал против этого, чтобы сделать ваш код более гибким и ориентированным на будущее.
Определение пользовательских типов ошибок может включать в себя написание большого количества шаблонов, но, к счастью, существует несколько библиотек, облегчающих эту проблему. Ошибка, цепочка ошибок и быстрая ошибка уже упоминались выше, но я хотел бы указать вам на ящик, который я написал, который включает в себя даже меньше стандартного шаблона, чем другие: custom_error. С его помощью вы можете написать:
#[macro_use] extern crate custom_error;
custom_error!{ MyError
Request{source: reqwest::Error} = "request error",
Url{source: url::ParseError} = "invalid url"
}
Ответ 2
К сожалению, в вашем случае вы не можете создать reqwest::Error
из других типов ошибок, если библиотека reqwest
не предоставляет способ сделать это (и, скорее всего, нет). Для решения этой проблемы, которая очень распространена, особенно в приложениях, использующих несколько библиотек, правильное решение будет одним из следующих:
Объявите свое собственное пользовательское перечисление со всеми ошибками, с которыми работает ваше приложение (или с одной подсистемой вашего приложения; степень детализации сильно зависит от проекта), и объявите преобразования From
из всех ошибок, с которыми вы работаете, в этот тип перечисления.
В качестве расширения этого подхода вы можете использовать error-chain
(или quick-error
, на котором в основном основана цепочка ошибок) для генерации таких пользовательских типов и преобразований в полу -автоматический способ.
Используйте специальный общий тип ошибки. В основном их два:
а. Box<Error>
где Error
определено в стандартной библиотеке.
б. Используйте тип Error
, определенный в ящике failure
.
Тогда оператор вопросительного знака сможет преобразовать любую совместимую ошибку в один из этих типов из-за различных реализаций черт Into
и From
.
Обратите внимание, что ящик failure
предназначен для определения ошибок, возникающих в сообществе Rust. Он не только предоставляет общий тип ошибки и черту (которая устраняет различные проблемы с чертой std::error::Error
; см., Например, здесь), но также имеет средства для определения собственных типов ошибок (например, с помощью failure_derive
), а также для отслеживания контекста ошибки, причин и создания трассировки. Кроме того, он старается быть максимально совместимым с существующими подходами к обработке ошибок, поэтому его можно легко использовать для интеграции с библиотеками, которые используют другие, более старые подходы (std::error::Error
, error-chain
, quick-error
). Поэтому я настоятельно рекомендую вам сначала использовать этот ящик, а не другие варианты.
Я уже начал использовать failure
в своих проектах приложений, и я просто не могу выразить, насколько проще и приятнее стала обработка ошибок. Мой подход заключается в следующем:
Определите тип Result
:
type Result<T> = std::result::Result<T, failure::Error>;
Используйте Result<Something>
везде, где можно вернуть ошибку, используя оператор вопросительного знака (?
) для преобразования между ошибками и функциями, такими как err_msg
или format_err!
или bail!
для создания собственных сообщений об ошибках.
Мне еще предстоит написать библиотеку с использованием failure
, но я думаю, что для библиотек было бы важно создать более конкретные ошибки, объявленные как enum, что можно сделать с помощью ящика failure_derive
. Для приложений, однако, тип failure::Error
более чем достаточно.
Ответ 3
Как уже говорилось
Владимир Матвеев, провал ящик должен быть отправной точкой. Вот мое решение:
use std::io;
use std::result;
use failure::{Backtrace, Fail};
/// This is a new error type manged by Oxide library.
/// The custom derive for Fail derives an impl of both Fail and Display.
#[derive(Debug, Fail)]
pub enum OxideError {
#[fail(display = "{}", message)]
GeneralError { message: String },
#[fail(display = "{}", message)]
IoError {
message: String,
backtrace: Backtrace,
#[cause]
cause: io::Error,
},
}
/// Create general error
pub fn general(fault: &str) -> OxideError {
OxideError::GeneralError {
message: String::from(fault),
}
}
/// Create I/O error with cause and backtrace
pub fn io(fault: &str, error: io::Error) -> OxideError {
OxideError::IoError {
message: String::from(fault),
backtrace: Backtrace::new(),
cause: error,
}
}
Это перечисление ошибок является расширяемым для будущих нужд.
Ответ 4
Обновление 10.10.2019
Ржавчина развивается быстро, поэтому можно добавить новый ответ! Мне очень нравится custom_error, но я думаю, что этот ящик теперь будет моим любимым человеком!
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[source] io::Error),
#[error("the data for key '{0}' is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
Это позволяет изменить io::Error
на DataStoreError::Disconnect
со знаком вопроса ?
.
источники: crates.io или
GitHub