Quick-error - это библиотека ржавчины, которая значительно упрощает определение типа ошибки. С момента последнего выпуска прошел почти год. Хотя большинство функций библиотеки достаточно стабильны, в этом выпуске есть еще один важный инструмент для исправления типов ошибок.
Задний план
Напоминаем: ящик предназначен для упрощения типов ошибок. Давайте посмотрим на пример:
quick_error! { #[derive(Debug)] pub enum MyError { Io(err: std::io::Error) { description(err.description()) display("IO error: {}", err) cause(err) from() } Utf8(err: std::str::Utf8Error) { description("utf8 error") display("decoding error: {}", err) } } }
Ключевое удобство здесь заключается в том, что у нас собраны все определения варианта ошибки, а не разбросаны между несколькими предложениями impl
и множеством match
statements.
Введение в частную ошибку
В приведенном выше определении ошибки есть загвоздка: если вы выставляете такое перечисление ошибок как общедоступный тип ошибки в своем ящике, каждый раз, когда вы добавляете или изменяете какой-либо вариант в нем, вы должны повышать основную версию вашего ящик. Натыкание на основную версию очень раздражает всех пользователей библиотеки.
Итак, общий шаблон - создать структуру-оболочку:
pub struct Error(ErrorEnum); enum ErrorEnum { Io(io::Error), Utf8(Utf8Error), }
Раньше вам приходилось каждый раз реализовывать все черты для Error
типа оболочки. Теперь у нас есть более простой синтаксис:
quick_error! { #[derive(Debug)] pub enum Error wraps ErrorEnum { Io(err: std::io::Error) { description(err.description()) display("IO error: {}", err) cause(err) from() } Utf8(err: std::str::Utf8Error) { description("utf8 error") display("decoding error: {}", err) } } }
Что определяет Error
как структуру кортежа с одним полем, содержащим ErrorEnum
(как в примере выше), а также предоставляет From<ErrorEnum> for Error
.
Как мне это использовать?
Приведенное выше определение уже полезно. Он обеспечивает отличные Display
и description()
. Но вы также можете использовать обычный код ржавчины, чтобы предоставить больше методов для создания или сопоставления типа ошибки:
impl Error { fn is_io_error(&self) -> bool {Quick-error crate is rarely updated. Mostly because it isn’t a library that should add new features every day. Still, we’re trying to keep track of good patterns of error reporting so you can concentrate on writing actual code. match self.0 { Error::Io(_) => true, _ => false, } } fn custom_error<E>(e: E) where E: Into<Box<Error + Send + Sync>> { Error::Io(io::Error::new(ErrorKind::Other, e) } }
Или вы даже можете добавить From
реализаций:
impl From<io::Error> for Error { fn from(err: io::Error) -> Error { Error::Io(err) } }
Примечание. Мы не распространяем From
реализации для типов оболочки, потому что это раскрывает слишком много внутренней структуры основной ошибки. Если у вас было Io(io::Error)
, а затем вы хотите добавить дополнительные сведения об ошибке: Io(io::Error, path: PathBuf)
, после изменения вы не можете предоставить надежные From<io::Error>
для такой ошибки, и если пользователи полагались на преобразование для своих нужд, это критическое изменение.
Почему не отдельный макрос?
Еще одна вещь, которую следует учитывать, - почему мы используем quick_error
макрос для задачи вместо того, чтобы добавлять отдельный макрос? Мы добавили простейшую форму ключевого слова wraps
. Но основная идея заключается в том, что так же, как мы собираем display/description/cause/from
предложений для каждого варианта ошибки, мы рассматриваем возможность добавления некоторых методов доступа, таких как этот:
quick_error! { #[derive(Debug)] pub enum Error wraps ErrorEnum { Io(err: std::io::Error) { # ... check is_io_error() # doesn't work yet construct from_io_error(err) # doesn't work yet } # ... } }
Или, возможно, добавление перечисления типа ошибки (аналогично тому, что в цепочке ошибок):
quick_error! { #[derive(Debug)] pub enum Error wraps ErrorEnum kind ErrorKind { # does not work Io(err: std::io::Error) { ... } Utf8(err: std::str::Utf8Error) { ... } } }
Но мы должны быть консервативными в отношении новых функций, потому что мы собираемся поддерживать их в течение многих лет.
Любые планы по макросам 1.1
Недавно в ржавчине 1.15 были представлены «Макросы 1.1» или «процедурные макросы», которые можно использовать для предоставления «derive (SomeTrait)». Возникает очевидный вопрос: можем ли мы использовать эту функцию для быстрой ошибки?
Что ж, хотя процедурные макросы отлично подходят для множества вещей, и хотя они позволяют генерировать произвольный код, похоже, что синтаксис атрибутов очень ограничен для наших целей. Т.е. написание кода внутри таких атрибутов, как:
#[derive(Error)] pub enum Error { #[error(display="format!('IO error: {}', e)")] Io(std::io::Error), }
… Выглядит не очень красиво.
С другой стороны, мы хотим сохранить обратную совместимость с существующим кодом. Тем не менее, изучение макросов 1.1 на предмет типов ошибок может быть отличным проектом для другого ящика (есть некоторые, но любой из них представляет собой мощную быструю ошибку?).
Вывод
Ящик с быстрыми ошибками обновляется редко. В основном потому, что это не библиотека, в которую каждый день нужно добавлять новые функции. Тем не менее, мы стараемся отслеживать хорошие шаблоны шаблонов типов ошибок, чтобы вы могли сосредоточиться на написании реального кода.