Implementing From<another trait's associated error type> for your enum

I have this error enum

pub enum Error<B: Backend> {
    Backend(B::Error)
}

And this is what I want to do

impl<B: Backend> From<B::Error> for Error<B>

But it's giving me

error[E0119]: conflicting implementations of trait `std::convert::From<cache::Error<_>>` for type `cache::Error<_>`
  --> src/cache.rs:30:1
   |
30 | impl<B: Backend> From<B::Error> for Error<B> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T> std::convert::From<T> for T;

This would be really nice to be able to do ? on it, which I am doing a lot, this is for a library by the way and also, B::Error is probably not the user's own type (usually the backend library's type i.e sqlx::Error)
So how would you do this idiomatically?

There's not a way to do it generically, and any feature that would allow it is a long way out.

On the practical front, this means that you have to have concrete implementations, unfortunately, which have to be in your crate or B::Error's crate (or the standard library).

Maybe (if a bound is required.)

pub enum Error<E: BackendError> {
    Backend(E)
}

How can I implement impl<E: BackendError> From<E> for Error? Won't that still error the same way?

What do you mean by concrete implementations? As in, I can't do it for traits?

As in, you can't use generics (even if they have a trait bound), you have to use concrete types like sqlx::Error. (I think this is what you mean by "do it for traits".)


Adding a bound for some new second trait on the associated type doesn't help as

  • You don't want to do that anyway, as again, only you or B::Error's crate could implement it
  • Coherence doesn't consider where clause constraints like that to boot, so even if it was viable, it doesn't gain you anything

I played around with some experimental features that expand coherence some, and couldn't get it to work with those even.

Compile error is because Backend and Backend::Error are allowed to be same type but switching to a unlinked trait allows From; just you may miss out elsewhere.

That's sad... As weird as this is I think I found a way

/// Used like `Error(sqlx::Error)`
pub struct Error<E: Display + Debug>(pub E);

impl<E: Display + Debug> From<Error<E>> for cache::Error<E> {
    fn from(err: Error<E>) -> Self {
        Self::Backend(err)
    }
}

pub enum Error<E: Display + Debug> {
    Backend(backend::Error<E>),
}

pub trait Backend<E: Display + Debug>: Sized {
    async fn set_current_user(&self, current_user: CurrentUser) -> Result<(), Error<E>>;
}

pub trait Cache<E: Display + Debug>: Backend<E> {
    async fn update(&self, event: &Event) -> Result<(), Error<E>> {
}

I hope there's a more elegant way but at least it works!

You could perhaps invert things so that the type parameter of your Error is what is stored. I'm not sure why you had that bound though. (Written before your latest comment.)

I don't understand how impl<B> From<B> for Error<B> { works, B could be anything right? Including Error itself, which would conflict so how come this works?

If B was Error<B>, then it's also Error<Error<B>> (via replacing on the right), and also Error<Error<Error<B>>>, ...

So we're looking at an infinitely recursive type, which is not possible in Rust.

I wish I had something concrete I could cite, but even trying to demonstrate this runs into problems.

Here's an example I suppose. Or here's something more directly showing that T<FreeGeneric> cannot unify with T<T<FreeGeneric>>.

Oh! I see it now, this is much simpler! What's weird is thiserror isn't even demanding a Display implementation on now, is this an error with thiserror? Rust Playground
Of course I'd use it like this but it's weird that this impl isn't required Rust Playground

This implementation is derived, as part of derive(Error).

It normally requires inners to implement Display though, so that it can implement it on the error itself Rust Playground

Ah, I see. In this case, Display is implemented wherever the generic type parameter is Display itself; if it isn't, thiserror's derive is simply not applicable - playground with a simple proof.

If we do cargo expand on this code, we'll see this explicitly:

impl<BackendError> std::fmt::Display for CacheError<BackendError> where
    BackendError: std::fmt::Display {
    // skipped
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.