Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I put an async function into a map in Rust?

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.

like image 747
Ukonn Ra Avatar asked Nov 05 '19 04:11

Ukonn Ra


People also ask

Can I use async await inside map?

If you use the async await function and console out the output, then you will find the arrays of promises that are still needed to be resolved. The map doesn't resolve the promises on its own but left the stuff for the developer to resolve. So, that means you can't use async-await in the map.

Does Rust support asynchronous?

The async/await syntax is supported directly by the Rust compiler. Many utility types, macros and functions are provided by the futures crate. They can be used in any async Rust application. Execution of async code, IO and task spawning are provided by "async runtimes", such as Tokio and async-std.

How is async implemented Rust?

Rust's implementation of async differs from most languages in a few ways: Futures are inert in Rust and make progress only when polled. Dropping a future stops it from making further progress. Async is zero-cost in Rust, which means that you only pay for what you use.

How async await works in Rust?

async / . await are special pieces of Rust syntax that make it possible to yield control of the current thread rather than blocking, allowing other code to make progress while waiting on an operation to complete.


1 Answers

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
}
like image 193
Ömer Erden Avatar answered Oct 14 '22 06:10

Ömer Erden