Search code examples
rustreqwestrust-result

How do I generify the error of a Rust Result<T, E> to Result<T, Box<dyn std::error::Error>>?


I am trying to generify a Result that is returned by the reqwest::blocking::get function. It returns a Result<reqwest::blocking::Response, reqwest::Error> but the function it is called in returns a Result<reqwest::blocking::Response, Box<dyn std::error::Error>.

  • Why does my first attempt fail to compile?
  • What is the most idiomatic way to make this conversion?

This is the first attempt:

fn get_example_fails() -> Result<Response, Box<dyn Error>> {
        let result = blocking::get("http://example.com");
        result.map_err(|error| Box::new(error))
    }

It has the following error which I do not know how to fix but feel it might be more idiomatic with some minor tweaking - but am not sure what to tweak:

error[E0308]: mismatched types
   --> src/bittrex.rs:143:9
    |
141 |     fn get_example_fails() -> Result<Response, Box<dyn Error>> {
    |                               -------------------------------- expected `Result<reqwes
t::blocking::Response, Box<(dyn StdError + 'static)>>` because of return type
142 |         let result = blocking::get("http://example.com");
143 |         result.map_err(|error| Box::new(error))
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait object `dyn StdError`, 
found struct `reqwest::Error`
    |
    = note: expected enum `Result<_, Box<(dyn StdError + 'static)>>`
               found enum `Result<_, Box<reqwest::Error>>`

This attempt compiles but seems verbose:

fn get_example_works() -> Result<Response, Box<dyn Error>> {
        let result = blocking::get("http://example.com");
        match result {
            Ok(resp) => Ok(resp),
            Err(error) => Err(Box::new(error)),
        }
    }

Solution

  • This is because the compiler is overzealous here. Notice that Box::new(error) simply puts error in a Box, so you get a strongly typed Box<reqwest:Error>, just as the compiler reports. But what you want is a trait object for dyn Error, which Box<reqwest:Error> could be, but the compiler does not make that type-weakening assumption. The fix is manually make that clear:

    fn get_example_fails() -> Result<Response, Box<dyn Error>> {
            let result = blocking::get("http://example.com");
            result.map_err(|error| Box::new(error) as Box<dyn Error>)
    }
    

    Notice the extra as Box<dyn Error>, which makes clear to the compiler that the value should be downcasted and where the fact that it is a concrete reqwest::Error that is in the Box should be erased.

    The reason why the second example compiles fine is type inference. In the first example, the type returned from the function will be the type returned from .map_err(), which is a Result<Response, Box<reqwest::Error>> - that's it. The type does not match, so you get an error. In the second example, the match constructs a whole new Result and Rust has to infer the T and E for that; so type inference kicks in. In this second case, the inference due to the function's return value is successful with respect to Box<dyn Error>. But with map_err(), the type inference can't "look into" the map_err()-body and weaken it's return type; its already too late, the type is fixed. That's why you have to do it manually as advised above.