avatar Nihil

Nichts Hsu

  • 首页
  • 子域
  • 分类
  • 标签
  • 归档
  • 关于
首页 Rust: Generator 已死,Coroutine 当立,Generator 又活了
文章

Rust: Generator 已死,Coroutine 当立,Generator 又活了

发表于 2023/11/07
作者 Nichts Hsu
6 分钟阅读

参考文献:

Generators are dead, long live coroutines, generators are back

Generalized coroutines

在曾经,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)
})
杂记, Rust
rust 编程语言
本文由作者按照 CC BY 4.0 进行授权
分享

最近更新

  • 『I Wanna』 Best Bye To 2016
  • [译] Rust 中的内联
  • [Rust] 幽灵索引类型与匿名结构体
  • [C++] 深入了解左值与右值
  • Android.bp 中启用 openmp
外部链接
  • 996.icu
  •  此博客的 Github 仓库
  •  Olimi 的个人博客

相关文章

2024/04/26

[Rust] 幽灵索引类型与匿名结构体

幽灵索引类型 假设我们有一个这样的类型: 1 2 #[derive(Debug, Clone, Copy)] struct Pair&lt;T, U&gt;(T, U); 并且,该类型保证 T 与 U 始终不会是相同的类型。那么,我们要如何设计一个统一的 get() 方法,使得下面的代码可以实现: 1 2 3 let pair = Pair(1, "hello"); let fi...

2024/02/18

Rust 不透明类型上的生命周期

此文撰写于 Rust stable 1.76.0(即 nightly 1.78.0)版本,由于 nightly 特性不受到 Rust 开发团队的保证,请谨慎甄别本文内容是否仍然适用。 抛出问题 在最前面,我们首先抛出一个问题,为什么下面的代码无法编译? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 use std::fmt::Debug; ...

2024/02/02

从另一个视角看 Rust HRTBs

HRTBs,即高阶 Trait 约束(Higher-Rank Trait Bounds),在 Rust 中是令很多初学者感到莫名其妙的概念,一手 for&lt;'a&gt; S&lt;'a&gt; 的语法更是使得原本就复杂的生命周期更加吓人。 但是,如果从另一个角度对 HRTBs 进行解剖,或许我们能看到不一样的东西。 首先,让我们考虑一个泛型和闭包的应用: 1 2 3 4 5 6 7...

Android.bp 中启用 openmp

于 Rust 1.75 稳定的 RPITIT 与 AFIT

© 2024 Nichts Hsu. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

热门标签

编程语言 教程 rust c++ android c++20 usb 翻译 linux qt

发现新版本的内容。