7

I wanted to make an auxiliary function for rendering templates in actix, here's how it looks:

fn render_response(
    tera: web::Data<Tera>,
    template_name: &str,
    context: &Context,
) -> impl Responder {
    match tera.render(template_name, context) {
        Ok(rendered) => HttpResponse::Ok().body(rendered),
        Err(_) => HttpResponse::InternalServerError().body("Failed to resolve the template."),
    }
}

I'm using it in views like this one below:

async fn signup(tera: web::Data<Tera>) -> impl Responder {
    let mut data = Context::new();
    data.insert("title", "Sign Up");

    render_response(tera, "signup.html", &data)
}

If the view is as simple as the one above, everything works fine, but if the view is slightly more complicated I get a problem:

async fn login(tera: web::Data<Tera>, id: Identity) -> impl Responder {
    let mut data = Context::new();
    data.insert("title", "Login");

    if let Some(id) = id.identity() {
        return HttpResponse::Ok().body(format!("Already logged in: {}.", id));
    }

    render_response(tera, "login.html", &data)
}

The error I get:

error[E0308]: mismatched types
   --> src\main.rs:101:5
    |
42  | ) -> impl Responder {
    |      -------------- the found opaque type
...
101 |     render_response(tera, "login.html", &data)
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `actix_web::HttpResponse`, found opaque type
    |
    = note:     expected type `actix_web::HttpResponse`
            found opaque type `impl actix_web::Responder`

I've tried to extract the return HttpResponse... to a separate function that also returns impl Responder, but I'm getting different error now:

error[E0308]: mismatched types
   --> src\main.rs:101:5
    |
42  | ) -> impl Responder {
    |      -------------- the found opaque type
...
101 |     render_response(tera, "login.html", &data)
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type
    |
    = note:     expected type `impl actix_web::Responder` (opaque type at <src\main.rs:104:28>)
            found opaque type `impl actix_web::Responder` (opaque type at <src\main.rs:42:6>)
    = note: distinct uses of `impl Trait` result in different opaque types

I don't really understand why it happens and how to fix it.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Djent
  • 2,877
  • 10
  • 41
  • 66
  • Returning `impl Responder` returns some concrete type implementing the `Responder` trait. You don't explicitly specify what type that is, but it's still a single, fixed type inferred by the compiler. The `login()` function returns two potentially different types, though – one opaque `impl Responder` on the one hand, and `HttpResponse` on the other hand. The actual, concrete type is `HttpResponse` in both cases, but the prototype of `render_template()` does not gurantee this. One option to fix this concrete example is to make `render_template()` return `HttpResponse` instead. – Sven Marnach Nov 05 '20 at 08:37
  • It's unclear whether that solution is general enough for all your use cases, though. If so, [Why can impl trait not be used to return multiple / conditional types?](https://stackoverflow.com/questions/52001592/why-can-impl-trait-not-be-used-to-return-multiple-conditional-types) has some suggestions how to solve this problem. – Sven Marnach Nov 05 '20 at 08:39
  • See also [the subsection on returning different types in the actix-web documentation](https://actix.rs/docs/handlers/#different-return-types-either). – Sven Marnach Nov 05 '20 at 08:43

1 Answers1

6

When a function returns something like impl Responder, it means that it returns some type that implements Responder. The compiler can deduce the type, but it is "opaque" to any caller, meaning you can't assume anything about what the type actually is. Even if the compiler has a bit more information, types are always matched at function boundaries. This is important so that humans can reason about the code at the function level, without having to hold all of the information about individual lines of a program in their heads.

A function also must only return one type, so when the compiler comes across a function like this:

async fn login(tera: web::Data<Tera>, id: Identity) -> impl Responder {
    let mut data = Context::new();
    data.insert("title", "Login");

    if let Some(id) = id.identity() {
        return HttpResponse::Ok().body(format!("Already logged in: {}.", id));
    }

    render_response(tera, "login.html", &data)
}

it sees the final statement returning an opaque impl Responder and the early return statement returning a concrete HttpResponse. Even though you know they are actually the same type, that's only because you happen to know how render_response is implemented. This is good because it prevents a change in an unrelated part of your code from breaking this function: you wouldn't want a change to the body of render_response to cause a type mismatch in login.

Change the return type of render_response to the concrete type and it will work:

fn render_response(
    tera: web::Data<Tera>,
    template_name: &str,
    context: &Context,
) -> HttpResponse;
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • 4
    Got it, I was thinking that this is something like an interface from other languages. Any type that implements the given trait is OK here. I understand it now. – Djent Nov 06 '20 at 06:26