2

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?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Fred Hors
  • 3,258
  • 3
  • 25
  • 71

1 Answers1

3

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.

Ian S.
  • 1,831
  • 9
  • 17
  • Thank you. What do you suggest as alternative to that common Go pattern? – Fred Hors May 08 '22 at 00:19
  • @FredHors Without knowing exactly what you're trying to accomplish, my first recommendation would be to question why you're trying to use this pattern and see if there's another solution. If you really need a function to construct closures given specific arguments, then I would recommend passing the `Router` as an argument into that function, and then you can just pass ownership of the closure directly to the `Router` to avoid having to explicitly name opaque types. – Ian S. May 08 '22 at 01:01