How to use the from trait automatically multiple times

Hi there!

I have multiple errortypes combined into an error enum inside a module of my project:

#[derive(Debug)]
pub enum ModuleError {
    A(DependencyErrorA),
    B(InternalError),
}

impl From<DependencyErrorA> for ModuleError {
    fn from(error: DependencyErrorA) -> Self {
            ModuleError::DependencyErrorA(error)
     }
}

// ... and so on

Now in the root of my project i have some kind of "Super Error" which shall combine all errors of all submodules. For now it looks something like this:

use crate::module::ModuleError;
 
#[derive(Debug)]
pub enum TopLevelError {
    ModuleError(ModuleError)
}

impl From<ModuleError> for TopLevelError {
    fn from(error: ModuleError) -> Self {
         TopLevelError::ModuleError(error)
    }
}

And I have a result type:

 pub type TopLevelResult<T> = Result<T, TopLevelError>;

How can i automatically 'cast' up the whole chain?

My code looks somewhat like this:

fn main() -> TopLevelResult<()> {
    let connection = do_stuff()?; // may unwrap to a DependencyErrorA
    let stuff = do_stuff_with_connection(&connection); // may unwrap to a InternalError
    manage_stuff(&connection, 1234)?; // may unwrap to a ModuleError
    let mut state = State::new(&connection);
    state.scan()?; // may unwrap to a DependencyErrorA
    loop { //.... and so on }
}

Do i have to reimplement the From traits i implemented on the ModuleError again? Or is there a nice way to convert a DependencyErrorA to a TopLevelError?

Thanks!

I'm afraid you can't, however you don't need to impl From transitively, for every possible combination of levels. Instead, you could just go through them explicitly:

TopLevelError::from(ModuleError::from(leaf_error))
1 Like

You can't. How would the compiler handle multiple "paths" up the chain?

Although we could emit an error when you create a set of conversions that is ambiguous, that tends to make backwards compatibility hard. For example, adding a new From impl in an upstream crate may now break downstream code even though adding a trait implementation is a backwards compatible change.

As long as you write down the hierarchy manually, there’s certainly the possibility to simplify such transitive From implementations with a macro. I’ve written one here, feel free to take a look. Going even further one could try to use procedural macros with global state that uses annotations on the From implementations themselves and thus doesn’t need to be given the hierarchy explicitly (making it perhaps a bit easier to keep the transitive implementations up to date if types are added to that hierarchy, etc.)

3 Likes

To add to this, I think that when using the my_result? syntax the propagation is automatic, even for multiple nested error enums.

No, it doesn’t do anything beyond a single From::from call on the error. Of course multple levels of ?-usage can result in multiple steps of From::from application, perhaps that’s what got you confused here.

Ah then I got it confused with the scenario in which a a From impl exists that effectively combines multiple From::from calls, kind of like a shorthand. In that scenario that would work.

Your marco looks like it could be the solution for the problem, but i dont quite understand yet how i could use this for enum variants instead of structs (as shown in your example)

This would be fine. But how can i use the nice ? operator?
As shown above, my code makes heavy usage of it, and i find it quite handy to not check every single statement for an error. Taking your example i'd probably need to do something like this all the, or am i wrong?

match function_which_returns_a_result() {
    Ok() => {/* if it worked */}
    Err(e) => return TopLevelError::from(ModuleError::from(e));
}

Here’s an example with enums --> link.

Have you thought of making a crate out of this? I'd definitely use it quite frequently in my projects.

I thought this macro up just today, but I too think it might be useful. I think it’ll be a good idea for a first crate of mine. There’s probably also reasonable ways to extend this in the future, e.g. supporting generics or the mentioned proc-macro route. Give me a day for figuring out how everything works around crates and I’ll reply again once it’s done ^^

Edit: Looks like you’ve already found it. For anyone else, here’s a link:

2 Likes