Skip to content

25.1 简介

生成器的语法很像前面讲过的闭包,但它与闭包有一个区别,即 yield 关键字。当闭包中有 yield 关键字的时候,它就不是一个闭包,而是一个生成器。

依然用示例来说话。假设我们要生成一个 Fibonacci 数列,用生成器可以这样写:

rust
// 方案一
#![feature(generators, generator_trait)]

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

fn main() {
    let mut g = || {
        let mut curr : u64 = 1;
        let mut next : u64 = 1;
        loop {
            let new_next = curr.checked_add(next);

            if let Some(new_next) = new_next {
                curr = next;
                next = new_next;
                yield curr; // <-- 新的关键字
            } else {
                return;
            }
        }
    };

    loop {
        unsafe {
            match g.resume() {
                GeneratorState::Yielded(v) => println!("{}", v),
                GeneratorState::Complete(_) => return,
            }
        }
    }
}

在这段代码中,构造了一个生成器,它长得跟闭包的样子差不多,区别只是它内部用到了yield关键字。它与 closure 类似的地方在于,编译器同样会为它生成一个匿名结构体,并实现一些 trait,添加一些成员方法。跟 closure 不同的地方在于,它的成员变量不一样,它实现的 trait 也不一样。在后面调用它时,不是采用类似闭包的那种调用方式,而是使用编译器自动生成的成员方法resume()resume()返回结果有两种可能性:一种是 Yielded 表示生成器内部yield关键字返回出来的东西,此时还可以继续调用resume,还有数据可以继续生成出来;另一种是Complete状态,表示这个生成器已经调用完了,它的值是内部return关键字返回出来的内容,返回了Complete之后就不能再继续调用resume了,否则会触发 panic。

生成器最大的特点就是,程序的执行流程可以在生成器和调用者之间来回切换。当我们需要暂时从生成器中返回的时候,就使用 yield 关键字;当调用者希望再次进入生成器的时候,就调用resume()方法,这时程序执行的流程是从上次 yield 返回的那个点继续执行。

上述程序的执行流程很有意思,它是这样的:

  • let g=||{...yield...};这句话是初始化了一个局部变量,它是一个生成器,此时并不执行生成器内部的代码;

  • 调用g.resume()方法,此时会调用生成器内部的代码;

  • 执行到yieldcurr;这条语时,curr变量的值为1,生成器的方法此时会退出,g.resume()方法的返回值是GeneratorState::Yielded(1),在main函数中,程序会打印出1

  • 循环调用g.resume()方法,此时再次进入到生成器内部的代码中;

  • 此时生成器会直接从上次退出的那个地方继续执行,跳转到 loop 循环的开头,计算curr next new_next这几个变量新的值,然后再到yield curr;这条语句返回;

  • 如此循环往复,一直到加法计算溢出,生成器调用了return;语句,此时main函数那边会匹配上GeneratorState::Complete这个分支,程序返回,执行完毕。

Released under the MIT License