У меня есть два типа ошибки 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.
Таким образом, я не могу создать свой тип обёртку что-бы заставить его нормальную ошибку.