[async/.await] How Executors run Futures<Output = T> where T != ()?

Hi there,

I've yet to fully understand the new async/.await patterns, framwork and concepts. In doing so I've seen that it is possible to have a Future with a specific Output type not being (). However, when looking at how Futures are spawned and run from the Executor I've seen that the Traitbounds in the respective methods always deal with Future<Output = ()> only. So I'm wondering how to ever run a Future to completion that actually gives a return type like u32. I've understood that an async fn will be magically converted into a Future with the return type that the function signature provides. But as this could not just being scheduled/spawned to the Executor I'd like to know what additional "magic" is going on there and allows to .await such a Future while retrieving the correct return type. Or is the return type of an .await not the actual value?

Assuming:

async fn add(current: u32) -> u32 {
  current + 1
}

Will:

let value = add(100).await;

actually return 101 in value ? Or is it a wrapped complex type behind the scenes ? As far as I understood the .await can only be placed inside an async function/block which would lead to the assumption that the compiler makes some clever Future chains that at the very top level creates an Future<Output = ()> that is ultimately spawnable ?

Thanks in advance for shedding any light on this :slight_smile:

The answer is that the executor doesn't run the future directly, but it runs a wrapper that contains the future somewhere inside.

Basically if you have a Future<Item = u32>, you could create a type like this:

struct MyFuture<F: Future<Output = u32>> {
     inner: F,
}
impl<F: Future<Output = u32>> Future for MyFuture<F> {
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        let pin_inner = unsafe { self.map_unchecked_mut(|myfut| &mut myfut.inner) };
        match pin_inner.poll(cx) {
            Poll::Ready(value) => {
                println!("{}", value);
                Poll::Ready(())
            },
            Poll::Pending => Poll::Pending,
        }
    }
}

fn print_u32<F: Future<Output = u32>>(fut: F) -> impl Future<Output = ()> {
    MyFuture { inner: fut }
}

playground

Then you can just wrap your future that resolves to an integer in the type above, and spawn the wrapped type.

Note that this:

async fn print_u32<F: Future<Output = u32>>(fut: F) {
    let val = fut.await;
    println!("{}", val);
}

is syntax sugar for the code block above.

I've already answered a similar question here, which you may also find helpful.

3 Likes

Your add function is syntax sugar for this:

struct AddFuture {
    current: u32,
}
impl Future for AddFuture {
    type Output = u32;
    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        Poll::Ready(self.current + 1)
    }
}

fn add(current: u32) -> impl Future<Output = u32> {
    AddFuture { current }
}

playground

Thanks for those great answers... This unties the knot in my head :slight_smile:

Just to add:
so using several .await statements after one another would de-sugar to .then chains of the generated Futures, right? So there is an "outer" Future with type () that wraps the chained Futures with their specific result type ? If this is the case, the compiler has a tough job to do :open_mouth:

Thx. again and best regards...

Yeah something like that. Every call to await is called an await-point, and there's also an implicit one at the start of the function. The anonymous future type it generates for the async fn is then an enum with a case for every await point, and the fields in each case, are the variables that exist at that point in the code. Then calling poll just has it move around between different states.

1 Like