Должна ли ошибка с источником включать этот источник в вывод дисплея? - PullRequest
3 голосов
/ 13 июля 2020

У меня есть тип ошибки, который подразумевает черту Error и скрывает основную причину ошибки, поэтому метод source возвращает Some(source). Я хочу знать, должно ли Display impl в моем типе ошибки включать описание этой исходной ошибки или нет.

Я вижу два варианта:

  1. Да, включить source в Display выводе, например, «Ошибка при открытии базы данных: нет такого файла»

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

Нет, просто распечатайте саму ошибку, например, «Ошибка при открытии базы данных», и оставьте ее клиентскому коду для просмотра и отображения source, если он хочет включить это в вывод.

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

Ящик с ошибочными данными (который мне очень нравится), кажется, намекает на предпочтение варианта 2, в этом варианте ошибки с полем source, но без атрибута display по умолчанию используется форматирование вывода Display, которое не включает source.

Может быть, мой настоящий вопрос вот в чем: какова цель метода source? Чтобы сделать цепочки ошибок форматирования более гибкими? Или Display действительно должен выводить все, что должно быть видно пользователю об ошибке, а source существует только для видимых разработчиками целей?

Я бы хотел увидеть некоторые подробные инструкции по этому поводу, в идеале в документация по признаку Error.

#[derive(Debug)]
enum DatabaseError {
    Opening { source: io::Error },
}

impl Error for DatabaseError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            DataBaseError::Opening { source } => Some(source),
        }
    }
}

impl fmt::Display for DatabaseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            DatabaseError::Opening { source } => {
                // ??? Should we include the source?
                write!(f, "Error opening database: {}", source)

                // ??? Or should we leave it to the caller to call .source()
                //     if they want to include that in the error description?
                write!(f, "Error opening database")
            }
        }
    }
}

1 Ответ

1 голос
/ 27 августа 2020

Два варианта печати исходной ошибки в реализации Display создают две школы дизайна. На данный момент, ни один из них не является более выраженным c, чем другой , хотя мнения в отношении обоих путей существуют, и в конечном итоге можно будет достичь консенсуса в сообществе в будущем или, возможно, уже так в будущем. контекст конкретного проекта или кодовой базы.

Руководящие принципы Rust API не содержат мнения о Display в ошибках, кроме C -GOOD-ERR , в котором просто говорится сообщение типа ошибки Display должно быть "строчными буквами без конечной пунктуации и обычно кратким" . Ожидается предложение по обновлению данного руководства, в котором разработчикам предлагается исключить source из Display подразумеваемых. Опять же, предложение не существует без некоторых трений.

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

1. Да, включите source в свой Display impl

Пример с SNAFU:

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Could not read data set token: {}", source))]
    ReadToken {
        #[snafu(backtrace)]
        source: ReadDataSetError,
    },
}

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

eprintln!("[ERROR] {}", err);

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

[ERROR] Could not read data set token: Could not read item value: Undefined value length of element tagged (5533,5533) at position 3548

2. Нет, не указывайте source на вашем Display impl

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Could not read data set token"))]
    ReadToken {
        #[snafu(backtrace)]
        source: ReadDataSetError,
    },
}

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

Ниже приводится простой пример. Для представления трассировки ошибки потребуются дополнительные logi c.

fn report<E: 'static>(err: E)
where
    E: std::error::Error,
    E: Send + Sync,
{
    eprintln!("[ERROR] {}", err);
    if let Some(cause) = err.source() {
        eprintln!();
        eprintln!("Caused by:");
        for (i, e) in std::iter::successors(Some(cause), |e| e.source()).enumerate() {
            eprintln!("   {}: {}", i, e);
        }
    }
}

Также стоит учитывать интерес интеграции с самоуверенными библиотеками. То есть некоторые ящики в экосистеме, возможно, уже сделали предположение о том, какой вариант выбрать. В anyhow отчеты об ошибках уже будут проходить по цепочке источников ошибок по умолчанию. При использовании anyhow для сообщения об ошибках вы должны не добавлять source, иначе вы можете получить раздражающий список повторяющихся сообщений:

[ERROR] Could not read data set token: Could not read item value: Undefined value length of element tagged (5533,5533) at position 3548

Caused by:
   0: Could not read item value: Undefined value length of element tagged (5533,5533) at position 3548
   1: Undefined value length of element tagged (5533,5533) at position 3548

Аналогично, * Библиотека 1053 *eyre предоставляет настраиваемую абстракцию отчетов об ошибках, но существующие средства отчетности об ошибках в экосистеме ящиков eyre также предполагают, что источник не распечатывается реализацией ошибки Display.

В в любом случае ...

Это решение играет роль только в сообщении об ошибках, а не в сопоставлении ошибок или обработке ошибок каким-либо другим образом. Существование метода source устанавливает цепную структуру для всех типов ошибок, которая может быть использована при сопоставлении с образцом и последующем управлении потоком программы. Метод Error::source имеет цель в экосистеме, независимо от того, как сообщается об ошибках.

...