rust, future & generator

rust, future & generator

  • future trait
  • fn() -> impl Future
  • async fn() 以及闭包
  • async {}
  • await

探讨future的不同写法。以及其中的差异。

future trait

自定义实现一个 trait 特征。需要实现 poll 方法。该方法返回一个枚举值: Poll, Ready 代表完成包裹了真正的返回数据;Pending 意味着任务还未完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub trait Future {
    /// The type of value produced on completion.
    #[stable(feature = "futures_api", since = "1.36.0")]
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
    /// Represents that a value is immediately ready.
    #[stable(feature = "futures_api", since = "1.36.0")]
    Ready(#[stable(feature = "futures_api", since = "1.36.0")] T),

    /// Represents that a value is not ready yet.
    ///
    /// When a function returns `Pending`, the function *must* also
    /// ensure that the current task is scheduled to be awoken when
    /// progress can be made.
    #[stable(feature = "futures_api", since = "1.36.0")]
    Pending,
}

Executor 会来调用 Futurepoll 方法根据返回值的不同,决定下一步的动作。

fn() -> impl Future

显式返回一个future trait。

1
2
3
fn my_fut() -> impl Future<Output = ()> {
    future::ready(())
}

async fn() 以及闭包

代码示例如下:

1
2
3
async fn ss() -> u32 {
    0
}

async 关键字,将函数的原型修改为返回一个future trait。然后将执行的结果包装在一个新的future中返回。大致相当于:

1
2
3
fn ss() -> impl Future<Output = u32> {
    async { 0 }
}

从这里可以看到,async fn 永远是返回 Ready(T),而没有办法返回 Pending的。

async {}

这个代码块实现了一个匿名的 Future Trait,大致相当于下面的逻辑:

1
2
3
4
5
6
7
impl Future for noname {
    type Output = u32;
    fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
        let ret = { ... };
        Poll::Ready(ret)
    }
}

await

将 async {} 的代码转换生成一个 Future trait。async {} 本身是惰性的,只有在await之后才会真正执行 Future trait。

生成逻辑在标准库中,可以看到是一个generator,名为GenFuture,实现了 Future Trait。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
where
    T: Generator<ResumeTy, Yield = ()>,
{
    struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T);

    // We rely on the fact that async/await futures are immovable in order to create
    // self-referential borrows in the underlying generator.
    impl<T: Generator<ResumeTy, Yield = ()>> !Unpin for GenFuture<T> {}

    impl<T: Generator<ResumeTy, Yield = ()>> Future for GenFuture<T> {
        type Output = T::Return;
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            // Safety: Safe because we're !Unpin + !Drop, and this is just a field projection.
            let gen = unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) };

            // Resume the generator, turning the `&mut Context` into a `NonNull` raw pointer. The
            // `.await` lowering will safely cast that back to a `&mut Context`.
            match gen.resume(ResumeTy(NonNull::from(cx).cast::<Context<'static>>())) {
                GeneratorState::Yielded(()) => Poll::Pending,
                GeneratorState::Complete(x) => Poll::Ready(x),
            }
        }
    }

    GenFuture(gen)
}

GenFuture 是一个实现了 Future 的 generator。因此在 Executor 执行这样的 Future 会进入相应的 poll 方法,接下来 gen.resume 将会进入执行 async {} 的内容。