rusterror-handlingtraits

How to implement a foreign trait with type parameters for a foreign type?


In my project I'm using a simple trait to add a context information to my custom error enums:

pub trait AddErrorContext<E,T> {
    fn with_context<'a>(self, f: impl FnOnce()->&'a str) -> std::result::Result<T, E>;
}

#[derive(Error,Debug)]
enum MyError {
...
}

impl<T, E> AddErrorContext<T, MyError> for std::result::Result<T, E>
where E: Into<MyError>,
{
    fn with_context<'a>(self, f: impl FnOnce() -> &'a str) -> std::result::Result<T,MyError> {
    ...
    }
}

This works perfectly if the trait AddErrorContext is defined in the same crate where it is used. If I'm trying to move it to other crate (to reuse it in multiple projects) I immediately get this error:

type parameter `T` must be covered by another type when it appears before the first local type (`MyError`)
implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

I kind of understand what does it mean (I'm implementing a foreign trait for a foreign generic type where not all generic parameters are know in the point of implementation), but I can't get how to sidestep this problem. I googled a lot but I only find solutions for simple types T, not for Result<T,_>.

According to the error message this is only because T is encountered before MyError. Can I somehow reverse it for the compiler?

Is it possible at all to implement a foreign trait on generic Result<> type?


Solution

  • You have a typo. The type parameters you've given to AddErrorContext in the implementation are in the wrong order. It should be:

    impl<T, E> AddErrorContext<MyError, T> for std::result::Result<T, E>
    

    So that MyError is matched to E. And then you'll find that it does compile.


    Why doesn't this run afoul of the "can't implement foreign trait on foreign type" rule? Because the orphan rules are more nuanced than that. From the concrete orphan rules in RFC #2451:

    Given impl<P1..=Pn> Trait<T1..=Tn> for T0, an impl is valid only if at least one of the following is true:

    • Trait is a local trait
    • All of
      • At least one of the types T0..=Tn must be a local type. Let Ti be the first such type.
      • No uncovered type parameters P1..=Pn may appear in T0..Ti (excluding Ti)

    [..]

    Under this proposal, the orphan rules continue to work generally as they did before, with one notable exception; We will permit impl<T> ForeignTrait<LocalType> for ForeignType<T>. This is completely valid under the forward compatibility rules set in RFC #1023. [..]

    What you have here matches the exception. Note that this has almost nothing to do with Result, but very much with the order of local type parameters for the foreign trait. If you had trait AddErrorContext<T, E> (opposite order of T and E), then there would be no way to implement it for your local error type and you'd have to use a workaround - like those mentioned in How do I implement a trait I don't own for a type I don't own?