Match Err(e) return Err(e) works but ? does not

Here is the signature for my function:

fn run(config: Config) -> Result<(), Box<dyn Error>>

In my function, I have:

    let server = match Server::new(ServerConfig {
        addr: (Ipv4Addr::UNSPECIFIED, config.port),
        ssl: None,
    }) {
        Ok(s) => s,
        Err(e) => return Err(e),
    };

And this works. But this does not compile:

    let server = Server::new(ServerConfig {
        addr: (Ipv4Addr::UNSPECIFIED, config.port),
        ssl: None,
    })?;

The error I get is:

error[E0277]: the size for values of type `dyn std::error::Error + Send + Sync` cannot be known at compilation time
  --> src/main.rs:37:7
   |
37 |     })?;
   |       ^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync`
   = note: required because of the requirements on the impl of `std::error::Error` for `Box<dyn std::error::Error + Send + Sync>`
   = note: required because of the requirements on the impl of `From<Box<dyn std::error::Error + Send + Sync>>` for `Box<dyn std::error::Error>`
   = note: required by `from`

I have tried reading all of the explanations, and will try again in the morning, but they just don't make sense to me. Why can't I propagate my error using the try operator?

For reference, Server is documented here: tiny_http::Server - Rust

Thanks

From the docs of tiny-http, I can see it has a rather weird return type: Result<Server, Box<Error + Send + Sync + 'static>>. What I think it should have been is Result<Server, Box<dyn Error + Send + Sync + 'static>>.

Minified example

The reason the match return works is because of Box<dyn Error + ...> coerces to Box<dyn Error>.
? fails however because there's no From impl that matches that coercion.

I think r.map_err(|e| e as Box<dyn Error>)? is the shortest way that works for an early return. But repeating the return type like that is a bit annoying.

Another option would be to change the return type of the outer function to exactly match the Server::new's error type, but whether that possible depend what other error the function can return.

That just an old depreciated way to say the same thing.

7 Likes

Are you saying that Rust doesn't know how to coerce Box<dyn std::error::Error + Send + Sync> to Box<dyn std::error:Error> ?

Are you saying that Rust would know how to perform that coercion if somebody had implemented a From trait (somehow, somewhere?) to do that coercion?

Would that trait look much different than the r.map_err(|e| e as Box<dyn Error>) code you showed?

That's very confusing to me. But I guess I can see that now.
What is the most normal idiomatic way to address this?

  • use match and return as I did initially?
  • Change the return type of my run() function to match the return type of Server::new() (and hope that any other errors I might throw match that as well)?
  • Use .map_err()? as you described?

This is really confusing... why does the e as Boxy<dyn Error> in your example work? Wouldn't that need a From trait to be implemented as well?

On the plus side, a huge part of the reason I decided to implement this project in Rust was to get more practice writing Rust code and learning the language's idiosyncrasies. That last part is working quite well for me so far :smile:

Thank you for the help. I hope that the tone of voice you hear as you read this is one of utter confusion and not one of confrontation.

No, Rust knows how to convert them with a coercion. The problem is that the From trait does not try to use coercions.

2 Likes

Hello @alice . You are always so helpful with your replies, and very patient with my questions. Thank you so much!

Reading Type coercions - The Rust Reference, I see that coercion is a somewhat complicated subject and will probably take me several rounds of trying stuff like this to get my head around.

It seems that the compiler must do something (coercion?) with this statement:

Err(e) => return Err(e),

that is different than what it does for the ? operator in:

let server = Server::new(ServerConfig {
        addr: (Ipv4Addr::UNSPECIFIED, config.port),
        ssl: None,
    })?;

It must attempt to use the From trait in the second case, and that isn't defined.

Hah! I see in Operator expressions - The Rust Reference that:

When [the question mark operator is] applied to values of the Result<T, E> type, it propagates errors. If the value is Err(e) , then it will return Err(From::from(e)) from the enclosing function or closure.

That's where the call to ::from() comes from, and that's not one of the traits defined by the return type from Server::new(). If the author(s) of tiny_http had added that trait to the return type, I never would have encountered this. Do I have that right?

Again, I remain curious as to the most idiomatic approach to dealing with this:

  • contact the author's of tiny_http and ask them to modify the return type of Server::new()
  • use a match statement like I did in my first example
  • use the .map_err() function as @spunit262 demonstrated
  • change the return type of my run() function to match that of Server::new()
  • something else?

Thanks again for the help.

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.