How can I add an async trait function to HashMap

Hi, I'm trying to implement a HTTP router and register an async function to HashMap, but it doesn't work. This is a minimal reproduction. I may be wrong in my understanding of async function and future to begin with, but can anyone help me with how to solve this?

use core::future::Future;
use core::pin::Pin;
use std::error::Error;
use std::collections::HashMap;

pub trait Handler<Req, E> {
    fn call(&self, req: Req) -> Pin<Box<dyn Future<Output = Result<(), E>> + Send + Sync>>;
}

impl<F: Send + Sync + 'static, Ret, Req, E> Handler<Req, E> for F
where
    F: Fn(Req) -> Ret + Send + Sync + 'static,
    Ret: Future<Output = Result<(), E>> + Send + Sync + 'static,
    E: Into<Box<dyn Error + Send + Sync>>,
{
    fn call(
        &self,
        req: Req,
    ) -> Pin<Box<dyn Future<Output = Result<(), E>> + Send + Sync>> {
        Box::pin(self(req))
    }
}

struct Map<Req, E: Into<Box<dyn Error + Send + Sync>> + 'static> {
    inner: HashMap<String, Box<dyn Handler<Req, E>>>,
}

impl<Req, E: Into<Box<dyn Error + Send + Sync>> + 'static> Map<Req, E> {
    fn add<H>(&mut self, k: String, h: H)
    where
        H: Fn(Req) -> Pin<Box<dyn Future<Output = Result<(), E>> + Send + Sync>>
            + Send
            + Sync
            + 'static,
    {
        self.inner.insert(k, Box::new(h));
    }
}

fn main() {
    let mut map = Map { inner: HashMap::new() };
    map.add("/index".to_string(), index);
}

async fn index(_: String) -> Result<(), std::convert::Infallible>{
    Ok(())
}
Compiling playground v0.0.1 (/playground)
error[E0271]: type mismatch resolving `<fn(String) -> impl Future {index} as FnOnce<(String,)>>::Output == Pin<Box<(dyn Future<Output = Result<(), _>> + Send + Sync + 'static)>>`
  --> src/main.rs:42:9
   |
42 |     map.add("/index".to_string(), index);
   |         ^^^ expected opaque type, found struct `Pin`
...
45 | async fn index(_: String) -> Result<(), std::convert::Infallible>{
   |                              ------------------------------------ checked the `Output` of this `async fn`, expected opaque type
   |
   = note: while checking the return type of the `async fn`
   = note: expected opaque type `impl Future`
                   found struct `Pin<Box<(dyn Future<Output = Result<(), _>> + Send + Sync + 'static)>>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0271`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

(playground)

The de-sugared signature for

async fn index(_: String) -> Result<(), std::convert::Infallible>

looks something like

fn index(_: String) -> impl Future<Output=Result<(), std::convert::Infallible>>

I think you rightfully want to deal with a boxed future as a return value instead (Box<dyn Future>), but the problem is you have some Fn returning some "unknown" type implementing Future (impl Future).

The easiest way to solve that problem is to loosen what add() accepts and then wrap the received Fn:

impl<Req, E: Into<Box<dyn Error + Send + Sync>> + 'static> Map<Req, E> {
    fn add<H, TFut>(&mut self, k: String, h: H)
    where
        H: Fn(Req) -> TFut + Send + Sync + 'static,
        TFut: Future<Output = Result<(), E>> + Send + Sync + 'static
    {
        let wrapper = move |req| {
            Box::pin(h(req))
        };
        self.inner.insert(k, Box::new(wrapper));
    }
}

(see playground)

I'm not completely sure all the other details of the code are what you really want, but it does compile now. In particular, I'm not sure if you want to be defining the error generic type that way, since you'd be stuck with every function added to the hashmap having the same error type (most likely you want to do conversion inside the wrapper closure too).

I got it, thanks! Your way is so good for me!

In particular, I'm not sure if you want to be defining the error generic type that way, since you'd be stuck with every function added to the hashmap having the same error type (most likely you want to do conversion inside the wrapper closure too).

I'm thinking about implementing a HTTP router. I want to be able to define the error type in each app. For example, one app have fn(req: Request) -> Result<Response, Infallible>, another app have fn(req: Request) -> Result<Response, hyper::Error>. FYI, it's still in progress but I work on it in my repository.

Thanks for your comment!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.