Add support for impl From<T> for ErrorEnum where T: Into<OneVariantsSource>
Prior comments:
- https://github.com/shepmaster/snafu/issues/481#issuecomment-3263186224
- https://github.com/shepmaster/snafu/issues/481#issuecomment-3267179024
- https://github.com/shepmaster/snafu/issues/481#issuecomment-3267273677
#[derive(Snafu)] enum Error { #[snafu(transparent)] #[snafu(source(forward))] DomainA { source: a::Error }, #[snafu(transparent)] DomainB { source: b::Error }, }Generated:
impl<T> From<T> for Error where T: Into<a::Error> { fn from(val: T) -> Self { Self::DomainA { source: val.into() } } } impl From<b::Error> for Error { fn from(val: b::Error) -> Self { Self::DomainB { source: val } } }Since Rust does not yet support specialization, I still need to call
map_errforT: Into<b::error>. However, a forward From implementation for the most frequently used variant can significantly reduces boilerplate.
Originally posted by @Huliiiiii in #481
I was continuing to edit my comment while you were responding (sorry!) so you likely missed this question:
Notably, I'm not understanding why you'd want such functionality for an enum variant — under what cases do you have multiple underlying error types that you want to collapse into a single error variant?
This is the actual code.
I define repositories and their errors using traits and associated types.
As a result, the error type returned by repositories is impl Into<infra::Error>.
This might be a poor abstraction, or perhaps I should re-design my errors.
That's why my original feature request was to provide a way to disable automatic From implementation generation.
#[derive(Debug, snafu::Snafu, ApiError)]
pub enum SignInError {
#[snafu(display("Already signed in"))]
#[api_error(
status_code = StatusCode::CONFLICT,
)]
AlreadySignedIn,
#[snafu(transparent)]
Authn { source: AuthnError },
#[snafu(transparent)]
Infra { source: infra::Error },
#[snafu(transparent)]
Validate { source: ValidateCredsError },
}
Work I've been doing locally would allow for you to write this:
#[derive(Debug, Snafu)]
enum SignInError {
#[snafu(display("Already signed in"))]
AlreadySignedIn,
#[snafu(transparent)]
Authn {
#[snafu(source(from(generic)))]
source: AuthnError
},
#[snafu(transparent)]
Infra { source: InfraError },
#[snafu(transparent)]
Validate { source: ValidateCredsError },
}
However, you would not be able to add a second #[snafu(source(from(generic)))] (e.g. in the Infra variant) because that would generate conflicting implementations of From:
impl<__SnafuSource> ::core::convert::From<__SnafuSource> for SignInError
where
__SnafuSource: ::core::convert::Into<AuthnError>,
{
#[track_caller]
fn from(error: __SnafuSource) -> Self {
let error: AuthnError = (Into::into)(error);
SignInError::Authn {
source: error,
}
}
}
impl<__SnafuSource> ::core::convert::From<__SnafuSource> for SignInError
where
__SnafuSource: ::core::convert::Into<InfraError>,
{
#[track_caller]
fn from(error: __SnafuSource) -> Self {
let error: InfraError = (Into::into)(error);
SignInError::Infra {
source: error,
}
}
}
This is a fundamental restriction imposed by Rust as nothing prevents a single type from being converted into both AuthnError and InfraError.
I define repositories and their errors using traits and associated types. As a result, the error type returned by repositories is
impl Into<infra::Error>.
This is one of the few cases where I actively recommend using a boxed error, such as by using ResultExt::boxed, although that still requires an extra method call (.boxed()), similar to the original .map_err(...) that you wanted to avoid.
If people are interested in trying this out, please check out #537 (including the docs) and use it in your project to ensure it works for you.
[patch.crates-io]
snafu = { git = 'https://github.com/shepmaster/snafu.git', branch = 'from-generic' }