Warp and response types and trait objects?

749 Views Asked by At

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.

1

There are 1 best solutions below

3
On

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,
    )))
}