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 进行授权
分享

最近更新

  • 从另一个视角看 Rust HRTBs
  • C++ Coroutine VS Rust Async
  • Rust 不透明类型上的生命周期
  • USB 2.0 与 USB 3.2
  • Rust 中的闭包递归与 Y 组合子
外部链接
  • 996.icu
  •  此博客的 Github 仓库
  •  Olimi 的个人博客

相关文章

2022/12/30

Rust 2022 年稳定的语法

概览 在整个 2022 年,Rust 一共发布了 1.58.0 ~ 1.66.0 共 9 个版本,让我们感谢 Rust 团队一整年的付出。 通常来说,大部分人都不是喜欢追着 Release Note 啃的类型,因此对于大部分人而言,Rust 的语法就只有书上写出来的那一些。这也是我撰写这篇文章的目的:总结和记录 Rust 整个 2022 年稳定的语法,让更多人意识到 “原来 Rust 还...

2023/03/28

[Rust] 当实例被移动时,究竟发生了什么?

前言 本文并不是移动语义的教程,并且本文假设你已经看过 the Book,已经了解了 Rust 中所有权的概念。 本文包含汇编和 MIR,但是并非需要了解汇编和 MIR 才能看懂。只要跟随本文的思路,即使以前不懂汇编和 MIR,也可以理解本文所表达的意图。 从汇编看移动 先简简单单 wrapper 一个 i32: use std::fmt::Debug; #[derive(Deb...

2023/05/11

C++ Coroutine VS Rust Async

在 C++20 中,我们有 Coroutine,在 Rust 中,我们有 Async。严格来说,二者之间没有完全等效的概念,但是我们可以找到一些相似之处,进而了解 C++ Coroutine 与 Rust Async 设计上的异同点。 由于我的上一篇文章介绍了 C++ 的 Coroutine,因此我们本文主要以 C++ Coroutine 的视角来看 Rust 的 Async。 为了更好...

Android.bp 中启用 openmp

于 Rust 1.75 稳定的 RPITIT 与 AFIT

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

本站采用 Jekyll 主题 Chirpy

热门标签

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

发现新版本的内容。