18

I cannot handle async functions when writing an async router for hyper.

This code:

use std::collections::HashMap;
use std::future::Future;

type BoxedResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
type CalcFn = Box<dyn Fn(i32, i32) -> dyn Future<Output = BoxedResult<i32>>>;

async fn add(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a + b)
}

async fn sub(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a - b)
}

fn main() {
    let mut map: HashMap<&str, CalcFn> = Default::default();
    map.insert("add", Box::new(add));
    map.insert("sub", Box::new(sub));

    println!("map size: {}", map.len());
}

Generates the following compiler error:

error[E0271]: type mismatch resolving `<fn(i32, i32) -> impl std::future::Future {add} as std::ops::FnOnce<(i32, i32)>>::Output == dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
  --> src/main.rs:17:23
   |
17 |     map.insert("add", Box::new(add));
   |                       ^^^^^^^^^^^^^ expected opaque type, found trait std::future::Future
   |
   = note: expected type `impl std::future::Future`
              found type `dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
   = note: required for the cast to the object type `dyn std::ops::Fn(i32, i32) -> dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`

error[E0271]: type mismatch resolving `<fn(i32, i32) -> impl std::future::Future {sub} as std::ops::FnOnce<(i32, i32)>>::Output == dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
  --> src/main.rs:18:23
   |
18 |     map.insert("sub", Box::new(sub));
   |                       ^^^^^^^^^^^^^ expected opaque type, found trait std::future::Future
   |
   = note: expected type `impl std::future::Future`
              found type `dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
   = note: required for the cast to the object type `dyn std::ops::Fn(i32, i32) -> dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`

It seems there is a conflict between impl Future and dyn Future, but I have no idea how to handle it.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ukonn Ra
  • 744
  • 6
  • 20

1 Answers1

15

This happens because impl Future is a concrete unique type while dyn Future is an abstract type. HashMap expects the abstract type since it can only hold instances of a single type.

If we can box the return type of the async functions, we will able to add these futures into a HashMap.

First we need to change the type of CalcFn:

type CalcFn = Box<dyn Fn(i32, i32) -> Pin<Box<dyn Future<Output = i32>>>>;

Then this can do the trick:

let mut map: HashMap<&str, CalcFn> = Default::default();
map.insert("add", Box::new(|a, b| Box::pin(add(a, b))));
map.insert("sub", Box::new(|a, b| Box::pin(sub(a, b))));

println!("map size: {}", map.len());

//map.get("add").unwrap()(2, 3).await

This complete example simplified Future's Item type, using an i32 instead of a Result. Please also check the full code for your case.

You can also use types from the futures crate like LocalBoxFuture and BoxFuture created by the FutureExt::boxed and FutureExt::boxed_local methods respectively:

use futures::future::{FutureExt, LocalBoxFuture}; // 0.3.5
use std::collections::HashMap;

type BoxedResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
type CalcFn = Box<dyn Fn(i32, i32) -> LocalBoxFuture<'static, BoxedResult<i32>>>;

async fn add(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a + b)
}

async fn sub(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a - b)
}

async fn example() {
    let mut map: HashMap<&str, CalcFn> = Default::default();
    map.insert("add", Box::new(|a, b| add(a, b).boxed()));
    map.insert("sub", Box::new(|a, b| sub(a, b).boxed()));

    println!("map size: {}", map.len());

    //map.get("add").unwrap()(2, 3).await
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ömer Erden
  • 7,680
  • 5
  • 36
  • 45
  • Full [example](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=7b42663108c1c18359c19c1fce16267d) with executor – Ömer Erden Nov 05 '19 at 10:47
  • 1
    See also [Why can impl trait not be used to return multiple / conditional types?](https://stackoverflow.com/q/52001592/155423); [How do I create a heterogeneous collection of objects?](https://stackoverflow.com/q/27957103/155423); [How can I have a collection of objects that differ by their associated type?](https://stackoverflow.com/q/28932450/155423). – Shepmaster Nov 05 '19 at 14:07