mrjbom
@mrjbom

Возврат собственного типа ошибки из main с печатью сообщения об ошибке?

У меня есть два типа ошибки ConfigError и GlobalError.
С помощью крейта thiserror эти типы реализуют типаж std::error::Error, а так-же GlobalError реализует типаж From для ConfigError, т.е. ConfigError легко преобразуется в GlobalError.
#[derive(Error, Debug)]
enum ConfigError {
    #[error("Failed to open config file: {source}")]
    NotOpened {
        #[from]
        source: std::io::Error
    },
    #[error("Wrong IP addr: {source}")]
    WrongAddr {
        #[from]
        source: std::net::AddrParseError
    }
}

#[derive(Error, Debug)]
enum GlobalError {
    #[error("Failed to parse config file.\n{source}")]
    ConfigParsing {
        #[from]
        source: ConfigError
    }
}

Я хочу иметь возможность с помощью оператора ? легко возвращать GlobalError из main с печатью читаемого сообщения об ошибке.
Я делаю это так:
fn main() -> Result<(), GlobalError> {
    let config = read_config_from_file()?;
    Ok(())
}

fn read_config_from_file() -> Result<Config, ConfigError> {
    // std::io::Error converts to ConfigError and returns
    let file_descriptor: FileDescriptor = open_config_file()?;
    Ok(Config)
}

// Returns std::io::Error
fn open_config_file() -> Result<FileDescriptor, std::io::Error> {
    Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
}

Это работает, но в консоли я получаю: "Error: ConfigParsing { source: NotOpened { source: Kind(PermissionDenied) } }".
Это мне не нравится, учитывая что у меня уже есть удобочитаемые сообщения об ошибке.

Что-бы они напечатались мне приходится делать как-то так:
fn main() {
    let config_result = read_config_from_file();
    if let Err(config_error) = config_result {
        let global_error: GlobalError = config_error.into();
        eprintln!("{global_error}");
        return;
    }
}

Печатает в консоль:
"Failed to parse config file.
Failed to open config file: permission denied"

Есть ли способ заставить первый вариант, с возвратом Result<(), GlobalError> из main, печатать удобочитаемые ошибки какие выводятся во втором?

UPD
Возврат Result из main обеспечивается трейтом std::process::Termination, в его impl происходит печать ошибки.
Переопределить impl Termination for Result нельзя, т.к. они оба внешние по отношению к моему крейту.
Я попробовал создать свой тип что-бы для него реализовать Termination и возвращать его:
struct MainResultWrapper(Result<(), GlobalError>);

impl std::process::Termination for MainResultWrapper {
    fn report(self) -> ExitCode {
        match self.0 {
            Ok(_) => std::process::ExitCode::SUCCESS,
            Err(global_error) => {
                eprintln!("{global_error}");
                std::process::ExitCode::FAILURE
            }
        }
    }
}

Тут я столкнулся с такой ошибкой:
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
Реализовать std::ops::FromResidual я не могу, т.к. это "Nightly-only experimental API."
Пробовал реализовать
From<Result<(), GlobalError>> for MainResultWrapper
, ожидая, что при использовании оператора будет преобразование ConfigError -> GlobalError -> MainResultWrapper, но это не помогает и компилятор по прежнему жалуется на отсутствие реализации FromResidual.

Таким образом, я не могу создать свой тип обёртку что-бы заставить его нормальную ошибку.
  • Вопрос задан
  • 174 просмотра
Решения вопроса 1
bingo347
@bingo347
Crazy on performance...
fn main() -> MainResultWrapper {
    MainResultWrapper(main_inner())
}

#[inline(always)]
fn main_inner() -> Result<(), GlobalError> {
    let config = read_config_from_file()?;
    Ok(())
}
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы