shepmaster / snafu

Easily assign underlying errors into domain-specific errors while adding context
https://docs.rs/snafu/
Apache License 2.0
1.4k stars 60 forks source link

Investigate switching `IntoError` from an associated type to generics #399

Open shepmaster opened 1 year ago

shepmaster commented 1 year ago

In #398, we discussed the possibility of changing IntoError to look something like

trait IntoError<S, C> {
    fn into_error(source: S, context: C) -> Self;
}

I think this would be needed to support having multiple #[snafu(from)] attributes.

shepmaster commented 1 year ago

As more-or-less expected, with the increased flexibility of the generics, certain pieces of code now become ambigous. For example, this code is no longer valid because nothing constrains e to a specific type:

let e = trigger().context(SomeSnafu).unwrap_err();
ErrorCompat::backtrace(&e)
shepmaster commented 1 year ago

As another attempt, I looked at making the trait closer in concept to std::ops::Add, where we take in a source error, add context to it, then get an output error:

// Library code

pub trait IntoError<C> {
    type Error;

    fn into_error(self, context: C) -> Self::Error;
}

struct NoneError;

// User code

#[derive(Debug)]
struct GenericError<T> {
    value: T,
}

// Generated code

struct GenericSnafu<__T0> {
    value: __T0,
}

impl<T, __T0> IntoError<GenericSnafu<__T0>> for NoneError
where
    __T0: ::core::convert::Into<T>,
{
    type Error = GenericError<T>;

    #[track_caller]
    fn into_error(self, context: GenericSnafu<__T0>) -> Self::Error {
        GenericError {
            value: ::core::convert::Into::into(context.value),
        }
    }
}

Unfortunately, that runs into a compiler error:

error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates
  --> src/lib.rs:24:6
   |
24 | impl<T, __T0> IntoError<GenericSnafu<__T0>> for NoneError
   |      ^ unconstrained type parameter