Storing futures

Hello!

I'm very new to Rust and running into a compiler error that isn't giving me enough clues about where to go next to learn more. Here's the example:

    let f1 = async {
        println!("Hello from f1");
    };
    let f2 = async {
        println!("Hello from f2");
    };

    let futures = vec![f1, f2];

I get the following error message, which I do not know how to decipher:

error[E0308]: mismatched types
43 |     let futures = vec![f1, f2];
   |                            ^^ expected generator, found a different generator
   |
   = note: expected type `impl std::future::Future` (generator)
              found type `impl std::future::Future` (generator)

There are clearly some subtleties about futures that I don't understand, but given that error message I don't even know where to start looking. Any tips or recommendations for what I should be reading up on to understand what this means?

(Also, even if storing futures this way is non-nonsensical, I'd still like to understand what is going on here)

Edit: I should also note, that if I just store one future in the vector, it compiles, adding to my confusion.

Thanks!

2 Likes

All async blocks have their own type, and no two async blocks have the same type. This means that you can't store diffrerent async blocks together without type erasure. To get type erasure, you can use Box<dyn Future<Output = _>>.

let futures: Box<dyn Future<Output = _>> = vec![Box::new(f1), Box::new(f2)];

Box isn't special in this way, any pointer like type will do.

2 Likes

Except Box<dyn Future> isn’t a future, you should use Pin<Box<dyn Future>> instead.

4 Likes

Aha, that makes sense, thanks!

Can you elaborate on why Box<dyn Future> isn't a future and why using Pin makes it a future?

(I'll go off and read about Pin now :-))

Future requires a function fn poll(self: Pin<&mut Self>, context: &mut Context) -> Poll<Self::Output> docs

Box<F> does implement Future, but only if F: Unpin + Future. Unpin basically means that Pin doesn't do anything. docs

Pin<Box<F>> also implements Future, given that F: Future. Note that Unpin is missing, this means any Future can be used inside. docs

You can easily create a pinned boxed future, like so:

let futures: Vec<Pin<Box<dyn Future<Output = _>>>>
    = vec![Box::pin(f1), Box::pin(f2)];
5 Likes

Note that for trivial futures, cost from the box itself(allocation, deallocation, and cache miss due to the indirection) can easily exceed the cost of the future itself. This is why Rust doesn't box everything by default unlike many other languages.

Note that you can avoid boxing altogether:

use ::std::{
    future::Future,
    pin::Pin,
    ptr,
    task::*,
};
use ::pin_utils::pin_mut;

fn main ()
{
    let f1 = async {
        println!("Hello from f1");
    };
    let f2 = async {
        println!("Hello from f2");
    };
    pin_mut!(f1, f2);
    let v: Vec<Pin<&mut dyn Future<Output = ()>>> =
        vec![f1, f2]
    ;
}

As a side not, you do not need to pin if the future is not self-referential, i.e., if there are no locals (including async fn function parameters) across await points). Since this, in practice is quite unlikely, it is better to work as if the Future were self-referential, by always Pinning a future before working on it.

1 Like

I think you meant, "does not box everything by default".

Yeah right, fixed.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.