状态机

在上一节中,我们讲到生成器执行到 yield 表达式时,会在这个 yield 点挂起,当再次激活生成器时会在挂起的 yield 点恢复运行,那么生成器是怎么保存在 yield 点挂起时的状态呢?

事实上,编译器会把生成器转化为一个状态机,状态机中会保存每一个 yield 点的生成器的执行状态。

假如我们写了一个如下所示的生成器:

#![feature(generators, generator_trait)]

use std::pin::Pin;
use std::ops::{Generator, GeneratorState};

fn main() {
    let mut gen = || {
        yield 1;
        yield 2;

        ()
    };

    loop {
        match Pin::new(&mut gen).resume(()) {
            GeneratorState::Yielded(y) => println!("Yielded: {}", y),
            GeneratorState::Complete(c) => {
                println!("Complete: {:?}", c);
                break;
            }
        }
    }
}

编译器会把生成器转化为下面的代码:

#![feature(generators, generator_trait)]

use std::mem;
use std::pin::Pin;
use std::ops::{Generator, GeneratorState};

fn main() {
    let mut gen = MyGenerator::new();

    loop {
        match Pin::new(&mut gen).resume(()) {
            GeneratorState::Yielded(y) => println!("Yielded: {}", y),
            GeneratorState::Complete(c) => {
                println!("Complete: {:?}", c);
                break;
            }
        }
    }
}

enum MyGenerator {
    Enter,
    State1(i32),
    State2(i32),
    Exit
}

impl<R> Generator<R> for MyGenerator {
    type Yield = i32;
    type Return = ();

    fn resume(self: Pin<&mut Self>, _arg: R) -> GeneratorState<Self::Yield, Self::Return> {
        let mut_gen = self.get_mut();
        match mem::replace(mut_gen, MyGenerator::Exit) {
            MyGenerator::Enter => {
                *mut_gen = MyGenerator::State1(1);
                GeneratorState::Yielded(1)
            }
            MyGenerator::State1(_) => {
                *mut_gen = MyGenerator::State2(2);
                GeneratorState::Yielded(2)
            }
            MyGenerator::State2(_) => {
                *mut_gen = MyGenerator::Exit;
                GeneratorState::Complete(())
            }
            MyGenerator::Exit => panic!("Generator has been completed.")
        }
    }
}

impl MyGenerator {
    fn new() -> Self {
        Self::Enter
    }
}

同时,由于每个 async 函数最终都会生成一个状态机,并且每个可执行文件都会捆绑一个异步运行时,这会导致异步的 Rust 代码在编译后产生更大的二进制体积,这也是 async Rust 的一个小缺点。