Rust: Generator 已死,Coroutine 当立,Generator 又活了
参考文献:
Generators are dead, long live coroutines, generators are back
在曾经,Rust 通过不稳定特性 generators 和 generator_trait 提供 Generator 功能,大体使用方法如下:
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
28
29
30
31
32
 
#![feature(generators, generator_trait)]
use std::ops::{Generator, GeneratorState};
use std::pin::Pin;
fn main() {
    // add_one 是一个 generator
    let mut add_one = |mut x: usize| loop {
        // `yield x + 1` 的值是下文中 resume 的参数,
        // 而 `x + 1` 则作为 resume 的返回值。
        // 如果不接收 yield 的值,
        // 那么只有第一次调用 resume 的参数是有效的。
        x = yield x + 1;
    };
    match Pin::new(&mut add_one).resume(0) {
        // 初次调用 add_one,0 作为参数 x 的值传入,
        // `yield x + 1` 被调用,resume 返回 1,
        // 此时 add_one 在 yield 处暂停,yield 未求值。
        GeneratorState::Yielded(x) => println!("yielded {x}"),
        // 事实上 generator 还可以 return value,
        // 并在此处通过 GeneratorState::Complete(v) 接收。
        _ => (),
    }
    match Pin::new(&mut add_one).resume(5) {
        // 第二次调用 add_one,5 作为上一次 `yield x + 1` 的值传入,
        // 使 x 变为 5,再一次执行 `yield x + 1`,
        // resume 返回 6。
        GeneratorState::Yielded(x) => println!("yielded {x}"),
        _ => (),
    }
}
 
 
Generator 类似闭包语法,因此也可以捕捉外部变量,也可以使用 move 关键字:
1
2
3
4
5
6
 
let v = [0, 1, 2, 3, 4];
let genrator = move || {
    for i in v {
        yield i;
    }
};
 
 
现在,这两个不稳定特性已经被移除了,原因是 Rust 团队认为该 Generator 实质已经完成了 Coroutine 的工作,因此,Rust 团队将过去所有 Generator 术语修改为 Coroutine,并引入了 coroutines 和 coroutine_trait 这两个新的不稳定特性。
从语法上来说,新的 Coroutine 和以前的 Generator 并没有什么很大的不同:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
#![feature(coroutines, coroutine_trait)]
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
fn main() {
    let mut add_one = |mut x: usize| loop {
        x = yield x + 1;
    };
    match Pin::new(&mut add_one).resume(0) {
        CoroutineState::Yielded(x) => println!("yielded {x}"),
        _ => (),
    }
    match Pin::new(&mut add_one).resume(5) {
        CoroutineState::Yielded(x) => println!("yielded {x}"),
        _ => (),
    }
}
 
 
但是,Rust 团队对 Generator 有了新的定义。现在,Generator 只是一个生成 Iterator 的快捷方式,这意味着 Generator 是一种没有参数、没有返回值的 Coroutine。
为了更方便构造 Generator,在 Rust edition 2024 中引入了 gen_blocks 特性,一个简单的斐波那契数列为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
#![feature(gen_blocks)]
fn fib() -> impl Iterator<Item = usize> {
    // gen block 返回一个 impl Iterator 的匿名类型。
    gen {
        let mut n1 = 1;
        let mut n2 = 1;
        let mut n3 = 2;
        yield n1;
        yield n2;
        loop {
            yield n3;
            n1 = n2;
            n2 = n3;
            n3 = n1 + n2;
        }
    }
}
fn main() {
    // 通过 take 截断前 10 个值。
    println!("{:?}", fib().take(10).collect::<Vec<usize>>());
}
 
 
若要运行上述代码,请使用 nightly 版本,在 cargo.toml 最上方添加 cargo-features = ["edition2024"],并使用 RUSTFLAGS="-Zunstable-options --edition 2024" cargo run 运行。或者,你也可以通过 godbolt 进行体验。
另外,正如前文所说,Generator 是 Coroutine 的一种,因此按照 Rust 团队的设想,gen block 理应是 Coroutine 的语法糖,上述代码应当解糖为(暂时不能编译):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
fn fib() -> impl Iterator<Item = usize> {
    std::iter::from_fn(|| {
        let mut n1 = 1;
        let mut n2 = 1;
        let mut n3 = 2;
        yield Some(n1);
        yield Some(n2);
        loop {
            yield Some(n3);
            n1 = n2;
            n2 = n3;
            n3 = n1 + n2;
        }
    })
}
 
 
同样,由于 gen block 被定性为 Coroutine 的语法糖,因此在引用外部变量时的行为也类似闭包,需要使用 move 移入(在 godbolt 上体验):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
#![feature(gen_blocks)]
// gen block 引用 `input`,因此返回值类型添加生命周期 `'_` 进行约束
fn gen_multiple(input: &[usize], multiple: usize) -> impl Iterator<Item = usize> + '_ {
    // 将 `multiple` move 进 gen block,否则以 `&usize` 的形式访问 `multiple`
    gen move {
        for i in input {
            yield i * multiple;
        }
    }
}
fn main() {
    let input = (0..10).collect::<Vec<usize>>();
    println!("{:?}", gen_multiple(&input, 2).collect::<Vec<usize>>());
}
 
 
另一方面,按照设想,gen block 也可以和 async block 组合,成为 Async Generator。由于 rustc 暂时还无法支持该特性,我们直接看官方例子简单了解一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
async gen {
  while let Some(item) = inner.next().await {
    yield func(item).await;
  }
}
// 按照设想,应该解糖为:
std::stream::from_fn(|ctx| {
  while let Some(item) = await_with!(inner.next(), ctx) {
    yield Ready(Some(await_with!(func(item), ctx)));
  }
  Ready(None)
})