I'm writing a new crate, and I want it to be useable with any implementation of a trait (defined in another crate). The trait looks something like this:
pub trait Trait {
type Error;
...
}
I have my own Error
type, but sometimes I just want to forward the underlying error unmodified. My instinct is to define a type like this:
pub enum Error<T: Trait> {
TraitError(T::Error),
...
}
This is similar to the pattern encouraged by thiserror, and appears to be idiomatic. It works fine, but I also want to use ?
in my implementation, so I need to implement From
:
impl<T: Trait> From<T::Error> for Error<T> {
fn from(e: T::Error) -> Self { Self::TraitError(e) }
}
That fails, because it conflicts with impl<T> core::convert::From<T> for T
. I think I understand why — some other implementor of Trait
could set type Error = my_crate::Error
such that both impl
s would apply — but how else can I achieve similar semantics?
I've looked at a few other crates, and they seem to handle this by making their Error
(or equivalent) generic over the error type itself, rather than the trait implementation. That works, of course, but:
- until we have inherent associated types, it's much more verbose. My
T
actually implements multiple traits, each with their ownError
types, so I'd now have to return types likeResult<..., Error<<T as TraitA>::Error, <T as TraitB>::Error>>
etc; - it's arguably less expressive (because the relationship to
Trait
is lost).
Is making my Error
generic over the individual types the best (most idiomatic) option today?
From
, you could use.map_err(Error::TraitError)?
instead of simply?
, this way the error type is alreadyError
instead ofT::Error
and?
will use the blanketimpl<T> From<T> for T
withT = Error
. – Filipe RodriguesFrom
trait just operates on a type, it doesn't know where it came from. What happens ifTraitA::Error
andTraitB::Error
have the same type? I still have to makeError
generic, but it can be generic over just one type, not every individual trait. – jbramleyT
withT: TraitA + TraitB
and have two variants inError
, oneTraitA(<T as TraitA>::Error)
and another asTraitB(<T as TraitB>::Error)
and.map_err(...)?
depending on which trait is used? Even if they had the same type, they'd be in distinct variants, and you don't need to implementFrom
forError
if using.map_err(...)?
. – Filipe Rodriguesmap_err()
suggestion. The only problem I've found so far is that it's still verbose (compared to?
), but the public API is very concise. Now I'm wondering why I haven't seen this used elsewhere. – jbramleyFrom
isn't viable, as it's whatmap_err
was designed for. If this has solved your problem, I'll post an answer summarising the solution. – Filipe Rodrigues