2

I have a Warp rejection handler, I'm using it like this,

.recover(handle_rejection)

It's declared like this,

pub async fn handle_rejection(err: Rejection) -> Result<impl warp::reply::Reply, Infallible> {

If both sides of the if statement are the same type,

if let Some(e) = err.find::<crate::api::error::UserError>() {
  Ok(warp::reply::with_status(
    warp::reply::reply(),
    warp::http::StatusCode::NOT_FOUND,
  ))
}
else {
  Ok(warp::reply::with_status(
    warp::reply::reply(),
    warp::http::StatusCode::NOT_FOUND,
  ))
}

Everything works fine, but if change one of those sides to be,

Ok(e.into_response())

It's no longer ok, I get this error on compilation,

error[E0308]: mismatched types
  --> src/api.rs:22:8
   |
22 |                   Ok(warp::reply::with_status(
   |  ____________________^
23 | |                     warp::reply::reply(),
24 | |                     warp::http::StatusCode::NOT_FOUND,
25 | |                 ))
   | |_________________^ expected struct `Response`, found struct `WithStatus`
   |

I don't understand that though, because that side didn't change, this should still satisfy impl warp::reply::Reply, what's the problem here?

I've tried different permutation of casting to the trait object explicitly like as warp::reply::Reply and as &dyn warp::reply::Reply but they don't work either.

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468

1 Answers1

2

The issue is that impl Trait is just a shorthand for some concrete type that implements Trait. So this:

fn foo() -> impl Bar {}

is the same as this:

fn foo() -> SomeConcreteTypeImplementingBar {}

Where SomeConcreteTypeImplementingBar is determined automatically (Thanks @Jmb for the correction).

Although not correct, it might help to think of it as this:

fn foo<B: Bar>() -> B

This is not the same, because the user specifies the type B, not the function, but it might be helpful for demonstration purposes. The real purpose of impl is to say "I'm going to return some type that implements Bar, but I won't tell you what that type is".

Eventually, Rust has to figure out the concrete type that is being returned. However, consider the following:

trait Bar {}

struct One;
impl Bar for One {}

struct Two;
impl Bar for Two {}

fn foo() -> impl Bar {
    if some_condition {
        One
    } else {
        Two
    }
}

What concrete type should the compiler choose? Well, it could either be One or Two, depending on what some_condition is! In this case, the compiler doesn't know which type to choose, so it throws an error.

This is the same error that you're getting. The two arms of your if statement are returning different types, so the compiler is throwing an error, telling you that it expects the type of both arms of the if statement to be either struct Response or struct WithStatus. To solve this issue, you can:

  • Create a new type that implements Reply that encapsulates both cases, then just return that type
  • Rework your function to only use one type. The above option is one case of this, but you could also use a warp built-in type
  • Box your return value. The resulting code would look like this:
pub async fn handle_rejection(err: Rejection) -> Result<Box<dyn warp::reply::Reply>, Infallible> {
if let Some(e) = err.find::<crate::api::error::UserError>() {
    Ok(Box::new(e.into_response()))
} else {
    Ok(Box::new(warp::reply::with_status(
        warp::reply::reply(),
        warp::http::StatusCode::NOT_FOUND,
    )))
}
S. Brown
  • 401
  • 3
  • 6
  • Note that the same error would be thrown for any if or match statement whose arms have different types – S. Brown Jun 03 '21 at 06:49
  • 4
    `fn foo() -> impl Bar` is _not_ the same as `fn foo() -> B` _at all_! In the second case the type `B` is chosen by the caller, whereas in the first case the return type is chosen by the function being implemented. `fn foo() -> impl Bar` is equivalent to `fn foo() -> SomeConcreteTypeImplementingBar` where `SomeConcreteTypeImplementingBar` is determined automatically. – Jmb Jun 03 '21 at 07:10
  • @Jmb you are correct, I'm sorry for the disinformation. I thought a generic parameter would be easier to understand, even though it, as you point out, is not the same as `impl`. I've edited the answer to clarify. Does it look good to you? – S. Brown Jun 04 '21 at 22:57