Convert between error types in different crates?

I have a function that needs to return an error of type A, and another function that returns an error of type B. Both error types are defined in crates that are not my own.

I need to convert a B into an A. I know I can do this manually using map_err (as shown in this answer), but I was wondering if there was a better way using From.

Here's an example:

// `PyResult` is an alias for `pub type PyResult<T> = Result<T, PyErr>;`
// `PyResult` is defined in a different crate (pyo3).
pub fn apply_local_change(&self) -> PyResult<PyAny> {
    // Want to use ? but I can't
    let foo =  func_that_returns_error()?;
    Ok(foo)
}

// FooError is also defined in a different crate
fn func_that_returns_error() -> FooError {
    // implementation
    ...
}

I tried doing

impl std::convert::From<FooError> for PyErr {
    fn from(err: FooError) -> PyErr {
        // implementation
       ...
    }
}

but this fails with "only traits defined in the current crate can be implemented for arbitrary types. Define and implement a trait or a new type instead".

What's the recommended approach here? Do I have to use map_err or or, or is there a cleaner way that allows me to use the ? operator.

2 Likes

You indeed won’t be able to write any trait implementations that would let you use ? in a way that converts one error type from an external crate into another error type from another external crate.

Some approaches from here:

  • Questioning the premises of your question: Why exactly do you need to

    what’s the context that requires this, some kind of call-back? If you’re just just using the function yourself, you might want to use your own local error type everywhere and convert both A and B into that one.

  • Using map_err isn’t too bad. You can still use ? operator, you just need to also add the map_err call. If the conversion is through a nice function with a short name, then the call .map_err(blah)? isn’t too bad. If you want to go further and this conversion is super common you could even add a method to Result through extension traits to make it even more concise, so instead of func_that_returns_error()? you’d write func_that_returns_error().py_err()? or something like that.

1 Like
  • what’s the context that requires this, some kind of call-back? If you’re just just using the function yourself, you might want to use your own local error type everywhere and convert both A and B into that one.

Unfortunately, I need to return a PyErr because this function is called from Python through some pyo3 Python-Rust interop magic.

I was thinking of using thiserror and writing a error type that has From implementations from FooError and PyErr into the common error type & then implementing std::convert::From<CommonError> for PyErr. Does that sound reasonable?

Yes this sounds reasonable to me. So the approach, in relation to my previous comment, could be to go the

route for the whole call hierarchy of what you’re doing in Rust land, and then make use of the

implementation in the last layer right where/before you use that interop magic. If the last layer also needs to handle some Result<_, FooError> stuff, either introduce another layer / wrapper function, or use some closures (as long as we don’t have try blocks)

I have no experience with pyo3 by the way :wink:

Thanks! I ended up doing the following:

trait ResultExt<T> {
    fn to_py_err(self) -> PyResult<T>;
}

impl<T> ResultExt<T> for Result<T, FooError> {
    fn to_py_err(self) -> PyResult<T> {
        match self {
            Ok(x) => Ok(x),
            // Convert `self` (which is of type `FooError`) into a useful error message
            Err(e) => Err(...)
        }
    }
}
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.