5

I am trying to store async functions in a vector, but it seems like impl cannot be used in the vector type definition:

use std::future::Future;

fn main() {
    let mut v: Vec<fn() -> impl Future<Output = ()>> = vec![];

    v.push(haha);
}

async fn haha() {
    println!("haha");
}
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/main.rs:4:28
  |
4 |     let mut v: Vec<fn() -> impl Future<Output = ()>> = vec![];
  |                            ^^^^^^^^^^^^^^^^^^^^^^^^

How do I write the type inside the vector?

I found that there may be a workaround by using a type alias, so I changed the code:

use std::future::Future;

type Haha = impl Future<Output = ()>;

fn main() {
    let mut v: Vec<fn() -> Haha> = vec![];

    v.push(haha);
}

async fn haha() {
    println!("haha");
}

This doesn't work either; this time the error occurs in the type alias:

error[E0658]: `impl Trait` in type aliases is unstable
 --> src/main.rs:3:1
  |
3 | type Haha = impl Future<Output = ()>;
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: for more information, see https://github.com/rust-lang/rust/issues/63063

error[E0308]: mismatched types
 --> src/main.rs:8:12
  |
8 |     v.push(haha);
  |            ^^^^ expected opaque type, found a different opaque type
  |
  = note: expected type `fn() -> Haha`
             found type `fn() -> impl std::future::Future {haha}`
  = note: distinct uses of `impl Trait` result in different opaque types

error: could not find defining uses
 --> src/main.rs:3:1
  |
3 | type Haha = impl Future<Output = ()>;
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

How do I fix it?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Kenneth
  • 403
  • 4
  • 12

2 Answers2

10

You cannot use the impl Trait this way. To be able to store different types that implement a trait into the same container you have to use dynamic dispatch, by storing something like Box<dyn Trait>.

In your particular case, you do not specify if you want to store the async functions themselves or the future generated by the async functions, the solution would be somewhat different.

To store just the futures, you write a container such as:

let mut v: Vec<Box<dyn Future<Output = ()>>> = vec![];

And then just call the function, box it and store it in the container:

v.push(Box::new(haha()));

If instead you want to store the async function itself, without calling it, you need a container with a double dyn:

let mut v2: Vec<Box<dyn Fn() -> Box<dyn Future<Output = ()>>>> = vec![];

Now, since your haha function does not implement this Fn trait you need an adaptor. A lambda function will do, but don't forget the double Box:

v2.push(Box::new(|| Box::new(haha())));

Unfortunately, with these solutions you will be able to create the vector, but not to .await for your futures. For that you need the futures to implement the Unpin marker. That guarantees to the compiler that the future will not move while it is running (if it did, the implementation would be totally unsafe). You could add the + Unpin requirement to the futures, but async fn are not Unpin so you could not fill the vector. The easiest way to fix it is to use this handy function from std:

pub fn into_pin(boxed: Box<T>) -> Pin<Box<T>>

for f in v2 {
    f().into_pin().await;
}

Unfortunately, it is still unstable. Fortunately, there is a From impl that does exactly the same. So you can just write:

for f in v2 {
    Pin::from(f()).await;
}

In your comment below you write this code to wait for the futures:

for f in v2 {
    async { f().await }
}

Note that an async block itself will evaluate to another future, so here you are just wrapping each future into another future, but nobody is waiting for that one. Actually you'll get a warning about it:

warning: unused implementer of std::future::Future that must be used.

Remember that in order to properly wait for all the futures you will need an async runtime.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Thank you very much. I try to use the way of "store the async function it self" but when try to run it, I got some problem. the run code is `for func in v2 { async { func().await }}`, the compiler said the Future doesn't implement `Pin` – Kenneth Oct 13 '19 at 10:25
  • @Kenneth: Ah, you want to wait for the `Futures` too? that makes sense. The problem is that `.await` requires the `Unpin` marker, but our code does not require it. You might add the `+ Unpin` to the `dyn Future` but then you will not be able to fill the vector, because `async functions` are not `Unpin`... Let me fix my answer in a minute... – rodrigo Oct 13 '19 at 10:49
  • Thank for the `Pin::from()` advice. I finally used a `async_std::task::block_on()` to run the async block, and it works like a charm. But here is another problem, what if the `fn haha` need a parameter to input? I mean if then function become `fn haha(word: &mut String){ println("haha {}", word);}`, which causing that we cannot push the function into the container because we have to call the function when pushing, and we don't have the parameter when pushing. – Kenneth Oct 13 '19 at 13:09
  • @Kenneth: You can capture the arguments into the lambda, maybe you'll need to move them: `v2.push(Box::new(move || Box::new(haha(xxx))));`. If you want to provide the argument when waiting for the future you'll need to adapt the `Fn` trait into the `Vec`. If you want to store Fns with different arguments in the same `Vec`... well you would need another layer of indirection (another trait, another dynamic dispatch, another `Box`) but that would be a totally different problem, unrelated to the `async` part. But beware that using a `&mut` argument in an async function may be tricky. – rodrigo Oct 13 '19 at 13:20
  • Oh, yes, I want to store functions in a vector, and provide different parameters to the functions when get the the functions out and execute them. Sounds like things I want to do with async functions will need lots of wrapping – Kenneth Oct 13 '19 at 13:32
6

rodrigo's answer is correct, but I'd prefer to use Box::pin and bake the Pin type into the API of the collection. This makes using the Future trait object (or closure trait object producing a Future trait object) easier:

use std::{future::Future, pin::Pin};

type PinFutureObj<Output> = Pin<Box<dyn Future<Output = Output>>>;

async fn collection_of_pinned_future_trait_objects() {
    let v: Vec<PinFutureObj<()>> = vec![
        Box::pin(haha()),
        Box::pin(hehe()),
        Box::pin(haha()),
        Box::pin(hehe()),
    ];

    for f in v {
        f.await
    }
}

async fn collection_of_closure_trait_objects() {
    let v: Vec<Box<dyn Fn() -> PinFutureObj<()>>> = vec![
        Box::new(|| Box::pin(haha())),
        Box::new(|| Box::pin(hehe())),
        Box::new(|| Box::pin(haha())),
        Box::new(|| Box::pin(hehe())),
    ];

    for f in v {
        f().await
    }
}

async fn haha() {
    println!("haha");
}

async fn hehe() {
    println!("hehe");
}

I'd also start introducing type aliases for the longer types.

In fact, this type alias already exists in the futures crate as LocalBoxFuture and can be created via FutureExt::boxed_local. There's also BoxFuture produced by FutureExt::boxed which adds common trait bounds.

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

async fn collection_of_pinned_future_trait_objects() {
    let v: Vec<LocalBoxFuture<'static, ()>> = vec![
        haha().boxed_local(),
        hehe().boxed_local(),
        haha().boxed_local(),
        hehe().boxed_local(),
    ];

    for f in v {
        f.await
    }
}

async fn collection_of_closure_trait_objects() {
    let v: Vec<Box<dyn Fn() -> LocalBoxFuture<'static, ()>>> = vec![
        Box::new(|| haha().boxed_local()),
        Box::new(|| hehe().boxed_local()),
        Box::new(|| haha().boxed_local()),
        Box::new(|| hehe().boxed_local()),
    ];

    for f in v {
        f().await
    }
}

async fn haha() {
    println!("haha");
}

async fn hehe() {
    println!("hehe");
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366