Подробное руководство по обработке ошибок в Rust

Подробное руководство по обработке ошибок в Rust

6 декабря 2022 г.

Сообщество Rust постоянно обсуждает обработку ошибок. В этой статье я попытаюсь объяснить, что это такое, почему и как мы должны его использовать.

Цель обработки ошибок

Обработка ошибок — это процесс, который помогает выявлять, отлаживать и устранять ошибки, возникающие во время выполнения программы. Это помогает обеспечить бесперебойную работу программы, предотвращая возникновение ошибок, и позволяет программе продолжать работу в оптимальном состоянии. Обработка ошибок также позволяет пользователям получать информацию о любых проблемах, которые могут возникнуть, и предпринимать корректирующие действия, чтобы предотвратить повторение ошибок в будущем.

Что такое результат?

Результатом является встроенное перечисление в стандартной библиотеке Rust. Он имеет два варианта Ok(T) и Err(E).

Image description

Result следует использовать как возвращаемый тип для функции, которая может столкнуться с ошибками. Значение Ok возвращается в случае успеха или значение Err в случае ошибки.

Реализация результата в функции.

Image description

Что такое обработка ошибок

Иногда мы используем функции, которые могут дать сбой, например, когда мы вызываем конечную точку из API или ищем файл. Эти типы функций могут столкнуться с ошибками (в нашем случае API недоступен или файл не существует). Существуют аналогичные сценарии, в которых мы используем обработку ошибок.

Image description

Пошаговое объяснение

  • Результат — это результат чтения имени пользователя из файловой функции. Из этого следует, что возвращаемое значение функции будет либо Ok, содержащим String, либо Err, содержащим экземпляр io::Error.

Есть вызов "File::open" внутри чтения имени пользователя из файла, который возвращает тип результата.

* Он может вернуть OK * Может возвращать ошибку

Затем код вызывает совпадение, чтобы проверить результат функции, и возвращает значение внутри "ok", если функция выполнена успешно, в противном случае возвращается значение Error.

Во второй функции read_to_string применяется тот же принцип, но в данном случае мы не использовали ключевое слово return, как видите, и в итоге возвращаем либо OK, либо Err.

Поэтому вы можете спросить: нужно ли мне писать эти блоки Match для каждого типа результатов?

Не волнуйтесь! Есть короткий путь!

Image description

Что такое вопросительный знак — ошибка распространения?

Согласно книге по языку Rust:

<цитата>

Оператор вопросительного знака (?) разворачивает действительные значения или возвращает ошибочные значения, передавая их вызывающей функции. Это унарный постфиксный оператор, который можно применять только к типам Result и Option.

Давайте объясним.

Вопросительный знак (?) в Rust используется для обозначения типа результата. Он используется для возврата значения ошибки, если операция не может быть завершена. Например, в нашей функции, которая читает файл, она может возвращать тип Result, где вопросительный знак указывает на то, что может быть возвращена ошибка, если файл не может быть прочитан, или, с другой стороны, окончательный результат. Другими словами, используется для короткого замыкания цепочки вычислений и раннего возврата, если условие не выполняется.

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("username.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

Каждый раз, когда вы видите ?, это возможный ранний возврат из функции в случае ошибки, иначе f будет хранить файл и обрабатывать содержащееся «ОК», а затем выполнение функции продолжится (аналогично функции разворачивания).

Зачем использовать ящики для обработки ошибок?

Стандартная библиотека не предоставляет всех решений для обработки ошибок. На самом деле, одна и та же функция может возвращать разные ошибки, что затрудняет их точную обработку. Личный анекдот, в нашей компании мы разработали Cherrybomb инструмент безопасности API, написанный на Rust, и нам нужно переписать хороший часть этого, чтобы иметь лучшую обработку ошибок.

Например:

Image description

Или одно и то же сообщение об ошибке может отображаться несколько раз.

Image description

Вот почему нам нужно определить собственное перечисление ошибок.

Image description

Тогда наша функция будет выглядеть так:

Image description

Настроить ошибки

Эта ошибка фокусируется на создании структурированных ошибок и имеет только одну характеристику, которую можно использовать для определения новых ошибок:

Thiserror — это библиотека обработки ошибок для Rust, которая предоставляет мощный, но лаконичный синтаксис для создания пользовательских типов ошибок.

В томе груза:[зависимости] thiserror = "1.0"

Это позволяет разработчикам создавать пользовательские типы ошибок и обработчики без написания большого количества шаблонного кода.

Благодаря этому набору ошибок мы можем настроить наши сообщения об ошибках.

Image description

Он также предоставляет функции для автоматического преобразования между пользовательскими типами ошибок и стандартными типами ошибок. Мы увидим это в следующей главе, посвященной динамическим ошибкам.

* Создание новых ошибок с помощью #[derive(Error)]. * Возможны перечисления, структуры с именованными полями, кортежные структуры и единичные структуры. * Display img генерируется для вашей ошибки, если вы предоставляете сообщения #[error("...")] в структуре или каждом варианте вашего перечисления и поддерживаете интерполяцию строк. *

Пример взят с сайта docs.rs:

Image description

Работа с динамическими ошибками

Если вы хотите иметь возможность использовать ‘?’, ваш тип Error должен реализовывать трейт From для типов ошибок ваших зависимостей. Ваша программа или библиотека может использовать множество зависимостей, каждая из которых имеет свою ошибку. У вас есть две разные структуры пользовательской ошибки, и мы вызываем функцию, которая возвращает один конкретный тип. Например: Описание изображения

Поэтому, когда мы вызываем нашу основную функцию, которая возвращает тип ErrorA, мы сталкиваемся со следующей ошибкой:

Image description

Поэтому одним из решений является реализация типажа From<ErrorB> для структуры ErrorA.

Наш код теперь выглядит так:

Image description

Другим решением этой проблемы является возврат динамических ошибок. Для обработки динамических ошибок в Rust, в случае значения Err, вы можете использовать оператор box, чтобы вернуть ошибку в виде Box (объект типажа типа Error). Это позволяет определять тип ошибки во время выполнения, а не во время компиляции, что упрощает работу с ошибками разных типов.

Затем Box можно использовать для хранения ошибок любого типа, в том числе из внешних библиотек или пользовательских ошибок. Затем Box можно использовать для распространения ошибки вверх по стеку вызовов, обеспечивая соответствующую обработку ошибки на каждом этапе.

Image description

Эта ошибка

Чтобы получить более чистый код, давайте воспользуемся этим блоком ошибок. Крейт thiserror может помочь обрабатывать динамические ошибки в Rust, позволяя пользователю определять пользовательские типы ошибок. Это делается с помощью макроса #[derive(thiserror::Error)]. Этот макрос позволяет пользователю определить пользовательский тип ошибки с определенным набором параметров, таких как код ошибки, сообщение и источник ошибки.

Затем пользователь может использовать этот тип ошибки для возврата соответствующего значения ошибки в случае динамической ошибки. Кроме того, контейнер thiserror также предоставляет несколько полезных методов, таких как display_chain, которые можно использовать для объединения нескольких ошибок в единую цепочку ошибок. Далее мы создали наш тип ошибки ErrorB , а затем использовали трейт From для преобразования ошибок ErrorB в наш пользовательский тип ошибки ErrorA. Если возникает динамическая ошибка, вы можете создать новый экземпляр вашего типа ошибки и вернуть его вызывающей стороне. См. функцию returns_error_a() в строке 13.

Image description

В любом случае ящик

Как бы то ни было, она была написана тем же автором, dtolnay, и выпущена на той же неделе, что и эта ошибка. anyhow может использоваться для возврата ошибок любого типа, которые реализуют std::error::Error. trait и будет отображать красиво отформатированное сообщение об ошибке, если программа выйдет из строя. Самый распространенный способ использования ящика — обернуть код в тип Result. Этот тип является псевдонимом для типа std::result::Result<T, E> и позволяет отдельно обрабатывать случаи успеха и неудачи.

Image description

Например, при возникновении ошибки вы можете использовать метод context(), чтобы предоставить дополнительную информацию об ошибке, или использовать метод with_chain(), чтобы объединить несколько ошибок. Пакет anyhow содержит несколько удобных макросов, упрощающих процесс создания и обработки ошибок. Эти макросы включают макросы bail!() и try_with_context!(). Первый можно использовать для быстрого создания значения ошибки, а второй — для переноса вызова функции и автоматической обработки любых возникающих ошибок.

Сравнение

Основное различие между Anyhow и ящиком Thiserror в Rust заключается в способе обработки ошибок. Anyhow позволяет обрабатывать ошибки с использованием любого типа, реализующего трейт Error, в то время как Thiserror требует, чтобы вы явно определяли типы ошибок с помощью макросов.

Anyhow — это библиотека обработки ошибок для Rust, которая предоставляет простой способ преобразования ошибок в унифицированный тип. Он позволяет писать краткий и мощный код обработки ошибок, автоматически преобразовывая множество различных типов ошибок в один общий тип.

В заключение, в Cherrybomb мы решили объединить их, чтобы создать собственный тип ошибки с этой ошибкой и управлять ею с помощью в любом случае ящик.


Также опубликовано здесь


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE