rustrust-axum

Type annotations needed for Axum handler with more than one generics


I'm trying to route a handler (get_request) with 2 generics (M: ApiTrait, C: ClientTrait)

let mut app = Router::new();
app = app.route(
    "/model",
    get(get_request),
);


pub async fn get_request<M: ApiTrait, C: ClientTrait>(
    params: Query<RequestParams>,
    client_opt: Option<Extension<C>>,
    State(api): State<Arc<M>>,
) -> Result<Response, ApiError> { ... }

but got errors:

error[E0283]: type annotations needed
   --> src/api/mod.rs:327:17
    |
327 |             get(get_request),
    |                 ^^^^^^^^^^^ cannot infer type of the type parameter `C` declared on the function `get_request`
    |
    = note: cannot satisfy `_: ClientTrait`
    = help: the following types implement trait `ClientTrait`:
              Client
              MockClient
note: required by a bound in `get_request`
   --> src/api/mod.rs:108:45
    |
108 | pub async fn get_request<M: ApiTrait, C: ClientTrait>(
    |                                          ^^^^^^^^^^^ required by this bound in `get_request`
help: consider specifying the generic arguments
    |
327 |             get(get_request::<S, C>),
    |                            ++++++++
```.

To resolve it, I need to specify one of the generics with
get(get_request::<_, Client>) instead of
get(get_request), despite that I want to keep it flexible so that I can use MockClient during test.
I have 2 questions:

  1. Any way I can keep the flexibility for both generics?
  2. Why can't Axum handler deal with more than one generics?

Solution

  • It is not about the number of generics, it is about whether the generic type parameters can be deduced from the calling context.

    The M parameter can be deduced because the Router is strongly-typed with the state. Presumably you have a .with_state() or other annotation that describes the router as being Router<Arc<MyState>> where M would be deduced as MyState.

    The C parameter cannot be deduced because there is nothing to deduce it from. Extensions aren't bound to the Router type, so it can't get it from there. The only constraint is that C: ClientTrait; how should the .route() call for the handler know to pick the real client or a mock client (or something else entirely)?

    You don't really have any options; the type cannot be deduced by the context so it must be specified.


    If you can somehow merge the ClientTrait object onto your state type, that could be a solution that would avoid explicit annotations. Otherwise...

    If you have a lot of handlers that need a ClientTrait like this, you could wrap it all function that does the router setup with a single C generic like this:

    fn make_router<C: ClientTrait + Clone + Send + Sync + 'static>() -> Router<Arc<MyState>> {
        let mut app = Router::new();
        app = app.route("/model", get(get_request::<_, C>));
        app
    }
    

    It still requires annotations, but by "bubbling it up" you'd only have to specify what C is once for your primary code and testing code.