Fred Hors
Fred Hors

Reputation: 4136

Why the error "the trait `Handler<_, _>` is not implemented for `()`" if I'm only returning a fn?

In Go I can return a function from a function, like this:

func newHandler(arg string) http.HandlerFunc {
    return service.Handler(arg)
}

// in other files

handler := newHandler(config.Arg)

router.route("/", func(group *router.Group) {
  group.GET("", handler)
})

In Rust is really hard to me today understand how to do this. I'm desperately trying with code like this:

use axum::{handler::Handler, response::Html, response::IntoResponse, routing::get, Router};

fn new_handler(arg: &str) {
  async fn custom_handler() -> impl IntoResponse {
      Html(source(HandlerConfig::new(arg)))
  }
}

let handler = new_handler(&config.arg);

let router = Router::new().route("/", get(handler))

but I get this error:

error[E0277]: the trait bound `(): Handler<_, _>` is not satisfied
   --> src\router.rs:22:50
    |
22  |         router = router.route("/", get(handler));
    |                                    --- ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for `()`
    |                                    |
    |                                    required by a bound introduced by this call
    |
note: required by a bound in `axum::routing::get`
   --> C:\Users\Fred\.cargo\registry\src\github.com-1ecc6299db9ec823\axum-0.5.4\src\routing\method_routing.rs:394:1
    |
394 | top_level_handler_fn!(get, GET);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`

If I use this code instead it works:

async fn custom_handler() -> impl IntoResponse {
  Html(source(HandlerConfig::new(arg)))
}

let router = Router::new().route("/", get(custom_handler))

Why that error?

Upvotes: 3

Views: 7148

Answers (1)

Ian S.
Ian S.

Reputation: 1951

So there are a couple things going on here. When you do the following:

fn new_handler(arg: &str) {
  async fn custom_handler() -> impl IntoResponse {
      Html(source(HandlerConfig::new(arg)))
  }
}

you're not "returning a function," you're just declaring an async function within another function and returning nothing. "Nothing" in rust is the unit type (), which is why you get an error about such and such trait not being implemented for (). Moreover, you cannot pass the argument arg in new_handler to any functions declared within new_handler. What you probably want to do is something like this:

fn new_handler(arg: &str) -> _ {
    || async {
        Html(source(HandlerConfig::new(arg)))
    }
}

However the issue is now the return type of new_handler. The type of that closure is, informally, impl Fn() -> (impl Future<Output = impl IntoResponse>) + '_. This is because the the "type" of the function async fn foo() -> T is essentially fn() -> FooFut, where FooFut is an opaque type which implements Future<Output = T>. If you try to put that as the return type, you will get a compiler error about impl Trait only being allowed in certain places. What it basically comes down to is that you can't nest existential types.

The pattern you're trying to mimic from Go is a non-pattern in rust, so I would avoid trying to translate your Go code so literally. The code that works looks a lot more Rusty, so I would stick with that.

Upvotes: 3

Related Questions