Appearance
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
这个分支,程序返回,执行完毕。