NiQin (blog: 泥芹) shared the aphorism --
I returned and saw under the sun, that the race is not to the swift, nor the battle to the strong, neither yet bread to the wise, nor yet riches to men of understanding, nor yet favour to men of skill; but time and chance happeneth to them all. -- 《圣经》

[Rust] Rust 迭代器(Iterator trait )的要诀和技巧

💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。

Summary: 迭代器(Iterator trait ),是 Rust 中最有用的 trait 之一。通过两个要诀:为自定义集合(collection)添加 iter() 函数;从不同类型的多个迭代器中,返回其中之一,我们对其做以认知。

Topics: rust 迭代器 iterator-trait

最近,敲 Rust 代码的过程中,对于其中迭代器(Iterator trait )的使用,遇到了一些不明所以的问题,求助于万能的搜索引擎,找到了一些资料。因此,对于 Rust 中迭代器(Iterator trait )的使用,有了一些新的认知。特此写文以记之。

主要参考自 Robin Moussu 的博客文章,以及他的 github 仓库

要诀1:为自定义集合(collection)添加 iter() 函数

如果您要创建自己的集合,例如一个封装动态数组 Vec 的结构体,那么,您可能需要为其提供一个 iter() 函数。这样,集合的使用者,就可以访问集合的元素,而不会暴露集合的实现细节。当然,您也可以创建一个新类型,为其实现 Iterator trait,但客观地讲,即使实现 Iterator trait 并不复杂,但也有诸多需要特别注意的细节!幸运的是,还有一种更简单的方法:

struct MyCollection {
    data: Vec<i32>, // 或者其它自身具有 `iter()` 方法的类型
    // ...
}

impl MyCollection {
    fn iter(&self) -> impl Iterator {
        self.data.iter()
    }
    // ...
}

上述代码可以吗?跑一跑看看有没有问题。

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
 --> src/lib.rs:8:19
  |
7 |     fn iter(&self) -> impl Iterator {
  |             ----- this data with an anonymous lifetime `'_`...
8 |         self.data.iter()
  |         --------- ^^^^
  |         |
  |         ...is captured here...
  |
note: ...and is required to live as long as `'static` here
 --> src/lib.rs:7:23
  |
7 |     fn iter(&self) -> impl Iterator {
  |                       ^^^^^^^^^^^^^
help: to declare that the `impl Trait` captures data from argument `self`, you can add an explicit `'_` lifetime bound
  |
7 |     fn iter(&self) -> impl Iterator + '_ {
  |                                     ^^^^

根据编译器的提示,是因为我忘了为 iter() 的返回类型设置生命周期标记 '_。我们来解决这个问题:

fn iter(&self) -> impl Iterator + '_ {
    self.data.iter()
}

这称之为生命周期省略,对于省略的详细描述,可以参考文档。这段代码,和如下代码是等价的:

fn iter<'a>(&'a self) -> impl Iterator + 'a {
    self.data.iter()
}

幸运的是,编译器足够聪明,能够理解匿名生存期 '_,并可以将其绑定到唯一引用 &self 的生命周期。

如果你不想自己编写上述代码,请移步 Rust 官方演练场(Playground)

要诀2:从不同类型的多个迭代器中,返回其中之一

如果您熟悉其它高级编程语言,您可能会尝试创建如下函数:

fn forward_or_backward<T>(v: &Vec<T>, forward: bool) -> impl Iterator + '_
{
    if forward {
        v.iter()
    } else {
        v.iter().rev()
    }
}

v.iter()v.iter().rev(),各自都返回一个实现了 Iterator trait 的类型。这段代码跑的通吗?

任何条件表达式(如 ifmatch、任何类型的循环等)的所有分支,其具体类型必须匹配。

我寄希望于编译器,希望它足够聪明,能够自动创建一个新类型,但目前情况并非如此。不过 Rust 语言团队已经在开发更重要、更令人兴奋的特性。

让我们看看编译器怎么说:

error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:6:9
  |
3 | /     if forward {
4 | |         v.iter()
  | |         -------- expected because of this
5 | |     } else {
6 | |         v.iter().rev()
  | |         ^^^^^^^^^^^^^^ expected struct `std::slice::Iter`, found struct `Rev`
7 | |     }
  | |_____- `if` and `else` have incompatible types
  |
  = note: expected type `std::slice::Iter<'_, _>`
           found struct `Rev<std::slice::Iter<'_, _>>`
help: you could change the return type to be a boxed trait object
  |
1 | fn forward_or_backward<T>(v: &Vec<T>, forward: bool) -> Box<dyn Iterator<Item=&T> + '_>
  |                                                         ^^^^^^^                       ^
help: if you change the return type to expect trait objects, box the returned expressions
  |
4 |         Box::new(v.iter())
5 |     } else {
6 |         Box::new(v.iter().rev())

编译器的建议并不难读懂,但是为什么我们要为动态内存分配和动态调度付出代价呢?如果我们使用静态调度呢?让我们来实现它:

首先,我们需要一个枚举来存储所有分支。在这里,我们完全可以使用类似于 either crate 的库来实现。但是,为了解释实现细节,我们将自己实现。

enum Either<Left, Right> {
    Left(Left),
    Right(Right),
}

现在,我们要为新类型实现 Iterator trait。当然,我们只能在枚举元素 LeftRight 都是迭代器的情况下,才能这样做。这两个迭代器必须产生相同类型的元素。

impl <Left, Right, Item> Iterator for Either<Left, Right>
where
    Left: Iterator<Item=Item>,
    Right: Iterator<Item=Item>,
{
    type Item = Item;
    fn next(&mut self) -> Option<Self::Item> {
        match self {
            Self::Left(it) => it.next(),
            Self::Right(it) => it.next(),
        }
    }
}

我们应该实现 nth()fold() 方法吗?

文档是这样讲的:

  • 需要注意到,迭代器提供了一个默认的方法实现,比如 nthfold,它们在内部调用 next
  • 但是,如果迭代器不调用 next,就可以更有效地进行计算。那么,也可以自定义 nthfold 的实现。

所以,nth()fold() 方法的实现不是必需的。但是,将对 nth()fold() 方法的调用,委托给 LeftRight 的实现,可能是个好主意,就像我们对 next() 方法所做的那样。因为 Iterator trait 的任何方法,都可以被专门实现为 LeftRight 类型。所以,最好对所有函数都这样做。

同样的逻辑,也可以应用于如 DoubleEndedIteratorExactSizeIterator,和 FusedIterator 等 trait。

让我们以 ExactSizeIterator trait 为例,我们希望将实现转发给基础类型:

impl<Left, Right> ExactSizeIterator for Either<Left, Right>
where
    Left: ExactSizeIterator,
    Right: ExactSizeIterator,
{
    fn len(&self) -> usize {
        match self {
            Self::Left(it) => it.len(),
            Self::Right(it) => it.len(),
        }
    }
    fn is_empty(&self) -> bool {
        match self {
            Self::Left(it) => it.is_empty(),
            Self::Right(it) => it.is_empty(),
        }
    }
}

代码是否正确?我们跑一下,看看编译器怎么说:

error[E0271]: type mismatch resolving `<Right as Iterator>::Item == <Left as Iterator>::Item`
  --> src/main.rs:50:19
   |
50 | impl<Left, Right> std::iter::ExactSizeIterator for Either<Left, Right>
   |      ----  -----  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `Right`, found type parameter `Left`
   |      |     |
   |      |     expected type parameter
   |      found type parameter
   |
   = note: expected associated type `<Right as Iterator>::Item`
              found associated type `<Left as Iterator>::Item`
   = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
   = note: required because of the requirements on the impl of `Iterator` for `Either<Left, Right>`

编译器检查后告知,应当明确指出,RightLeft 都是产生相同类型的迭代器。我们做些小修改:

impl<Left, Right, T> std::iter::ExactSizeIterator for Either<Left, Right>
where
    Left: std::iter::ExactSizeIterator + Iterator<Item=T>,
    Right: std::iter::ExactSizeIterator + Iterator<Item=T>,

好像该做的都做了?我们开始测试。

fn main() {
    let v = vec![0, 1, 3, 7];

    let forward: Vec<_> = forward_or_backward(&v, true).collect();
    let backward: Vec<_> = forward_or_backward(&v, false).collect();
    println!("forward: {:?}", forward); // 0, 1, 3, 7
    println!("backward: {:?}", backward); // 7, 3, 1, 0
}

又出错了:

error[E0277]: `<impl Iterator as Iterator>::Item` doesn't implement `Debug`
  --> src/main.rs:87:31
   |
87 |     println!("forward: {:?}", forward); // 0, 1, 3, 7
   |                               ^^^^^^^ `<impl Iterator as Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `Debug`
   |
   = help: the trait `Debug` is not implemented for `<impl Iterator as Iterator>::Item`
   = note: required because of the requirements on the impl of `Debug` for `Vec<<impl Iterator as Iterator>::Item>`
   = note: required by `std::fmt::Debug::fmt`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

太奇怪了,我们是对整数进行迭代,整数本身就实现了调试特性的,为什么这儿不起作用呢?

让我们想一想……

当我们实现 forward_or_backward() 时,我们并没有对迭代项的类型进行转发。我们上面的代码是:

fn forward_or_backward<T>(v: &Vec<T>, forward: bool) -> impl Iterator + '_

可以看到,迭代器产生项的类型是未知的。如果我们在收集 vector 的值时,不使用类型推断。那么,很明显:

let forward: Vec<i32> = forward_or_backward(&v, true).collect();
error[E0277]: a value of type `Vec<i32>` cannot be built from an iterator over elements of type `<impl Iterator as Iterator>::Item`
  --> src/main.rs:85:59
   |
85 |     let forward: Vec<i32> = forward_or_backward(&v, true).collect();
   |                                                           ^^^^^^^ value of type `Vec<i32>` cannot be built from `std::iter::Iterator<Item=<impl Iterator as Iterator>::Item>`
   |
   = help: the trait `FromIterator<<impl Iterator as Iterator>::Item>` is not implemented for `Vec<i32>`

所以,我们仅需要修改 forward_or_backward 的声明:

fn forward_or_backward<T>(v: &Vec<T>, forward: bool) -> impl Iterator<Item=T> + '_

现在,代码正确了。

如果你不想自己编写上述代码,请移步 Rust 官方演练场(Playground)

关于迭代器,还有很多要掌握的,它是 Rust 中最有用的 trait 之一,但今天就到此为止。

谢谢您的阅读!

原文链接:Rust iterators tips and tricks


Related Articles

  1. [Rust] RustHub.org:基于 Rust-Web 技术栈,及 image-rs、fluent-rs、rhai-script ……
  2. [WebAssembly] yew SSR 服务器端渲染
  3. [Rust] async-std 创建者对于最近“项目是否已死?”,移除对其支持等的答复
  4. [Rust] Rust 1.56.0 版本和 Rust 2021 版次发布,新特性一览,及项目的迁移、升级
  5. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 博客应用的体验报告
  6. [Rust] Rust 官方周报 399 期(2021-07-14)
  7. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(5)- 构建 HTTP 请求、与外部服务器通信的两种方法
  8. [Rust] Rust 官方周报 398 期(2021-07-07)
  9. [Rust] Rust 官方周报 397 期(2021-06-30)
  10. [Rust] Rust 官方周报 396 期(2021-06-23)
  11. [Rust] Rust 官方周报 395 期(2021-06-16)
  12. [Rust] Rust 1.53.0 明日发布,关键新特性一瞥
  13. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(3)- rhai 脚本、静态/资源文件、环境变量等
  14. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(2)- 获取并解析 GraphQL 数据
  15. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(1)- crate 选择及环境搭建
  16. [Rust] Rust 官方周报 394 期(2021-06-09)
  17. [Rust] Rust web 前端库/框架评测,以及和 js 前端库/框架的比较
  18. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(4)- 获取 GraphQL 数据并解析
  19. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(3)- 资源文件及小重构
  20. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(2)- 组件和路由
  21. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(1)- 起步及 crate 选择
  22. [Rust] Rust 官方周报 393 期(2021-06-02)
  23. [Rust] Rust 官方周报 392 期(2021-05-26)
  24. [Rust] Rust 中,对网址进行异步快照,并添加水印效果的实践
  25. [Rust] Rust 官方周报 391 期(2021-05-19)
  26. [Rust] Rust,风雨六载,砥砺奋进
  27. [Rust] 为什么我们应当将 Rust 用于嵌入式开发?
  28. [Rust] Rust 官方周报 390 期(2021-05-12)
  29. [Rust] Rust + Android 的集成开发设计
  30. [Rust] Rust 1.52.1 已正式发布,及其新特性详述
  31. [Rust] 让我们用 Rust 重写那些伟大的软件吧
  32. [Rust] Rust 1.52.0 已正式发布,及其新特性详述
  33. [Rust] Rust 官方周报 389 期(2021-05-05)
  34. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(4) - 变更服务,以及小重构
  35. [Rust] Rust 1.52.0 稳定版预发布测试中,关键新特性一瞥
  36. [Rust] Rust 生态中,最不知名的贡献者和轶事
  37. [Rust] Rust 基金会迎来新的白金会员:Facebook
  38. [Rust] Rustup 1.24.1 已官宣发布,及其新特性详述
  39. [Rust] Rust 官方周报 388 期(2021-04-28)
  40. [Rust] Rust 官方周报 387 期(2021-04-21)
  41. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(4)- 变更服务,以及第二次重构
  42. [Rust] Rustup 1.24.0 已官宣发布,及其新特性详述
  43. [Rust] basedrop:Rust 生态中,适用于实时音频的垃圾收集器
  44. [Rust] Rust 编译器团队对成员 Aaron Hill 的祝贺
  45. [Rust] Jacob Hoffman-Andrews 加入 Rustdoc 团队
  46. [机器人] 为什么应将 Rust 引入机器人平台?以及机器人平台的 Rust 资源推荐
  47. [Rust] rust-lang.org、crates.io,以及 docs.rs 的管理,已由 Mozilla 转移到 Rust 基金会
  48. [Rust] Rust 官方周报 386 期(2021-04-14)
  49. [Rust] Rust 编译器(Compiler)团队 4 月份计划 - Rust Compiler April Steering Cycle
  50. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(3) - 重构
  51. [Rust] 头脑风暴进行中:Async Rust 的未来熠熠生辉
  52. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(2) - 查询服务
  53. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务 - 起步及 crate 选择
  54. [Rust] Rust 2021 版本特性预览,以及工作计划
  55. [Rust] Rust 用在生产环境的 42 家公司
  56. [Rust] 构建最精简的 Rust Docker 镜像
  57. [Rust] Rust 官方周报 385 期(2021-04-07)
  58. [Rust] 使用 Rust 做异步数据采集的实践
  59. [Rust] Android 支持 Rust 编程语言,以避免内存缺陷
  60. [Rust] Android 平台基础支持转向 Rust
  61. [Rust] Android 团队宣布 Android 开源项目(AOSP),已支持 Rust 语言来开发 Android 系统本身
  62. [Rust] RustyHermit——基于 Rust 实现的下一代容器 Unikernel
  63. [Rust] Rustic:完善的纯粹 Rust 技术栈实现的国际象棋引擎,多平台支持(甚至包括嵌入式设备树莓派 Raspberry Pi、Buster)
  64. [Rust] Rust 迭代器(Iterator trait )的要诀和技巧
  65. [Rust] 使用 Rust 极致提升 Python 性能:图表和绘图提升 24 倍,数据计算提升 10 倍
  66. [Rust] 【2021-04-03】Rust 核心团队人员变动
  67. [Rust] Rust web 框架现状【2021 年 1 季度】
  68. [Rust] Rust 官方周报 384 期(2021-03-31)
  69. [Rust] Rust 中的解析器组合因子(parser combinators)
  70. [生活] 毕马威(KPMG)调查报告:人工智能的实际采用,在新冠疫情(COVID-19)期间大幅提升
  71. [Python] HPy - 为 Python 扩展提供更优秀的 C API
  72. [Rust] 2021 年,学习 Rust 的网络资源推荐(2)
  73. [Rust] 2021 年,学习 Rust 的网络资源推荐
  74. [生活] 况属高风晚,山山黄叶飞——彭州葛仙山露营随笔
  75. [Rust] Rust 1.51.0 已正式发布,及其新特性详述
  76. [Rust] 为 Async Rust 构建共享的愿景文档—— Rust 社区的讲“故事”,可获奖
  77. [Rust] Rust 纪元第 382 周最佳 crate:ibig 的实践,以及和 num crate 的比较
  78. [Rust] Rust 1.51.0 稳定版本改进介绍
  79. [Rust] Rust 中将 markdown 渲染为 html
  80. [生活] 国民应用 App 的用户隐私数据窥探
  81. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(3)- 重构
  82. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(2)- 查询服务
  83. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(1)- 起步及 crate 选择
  84. [Rust] Rust 操控大疆可编程 tello 无人机

Topics

rust(84)

graphql(17)

rust-官方周报(17)

webassembly(16)

wasm(10)

tide(9)

async-graphql(9)

yew(9)

rust-web(8)

rust-官方博客(8)

this-week-in-rust(6)

mysql(5)

actix-web(5)

rbatis(5)

android(4)

mongodb(3)

json-web-token(3)

jwt(3)

cargo(3)

技术延伸(3)

rust-wasm(3)

trunk(3)

handlebars(3)

rhai(3)

async-std(3)

用户隐私(2)

学习资料(2)

python(2)

ai(2)

人工智能(2)

postgresql(2)

rust-compiler(2)

rust-基金会(2)

rust-foundation(2)

rustup(2)

rust-toolchain(2)

rust-工具链(2)

rust-游戏开发(2)

rust-区块链(2)

rust-2021(2)

graphql-client(2)

surf(2)

rust-game(2)

rusthub(2)

tello(1)

drone(1)

无人机(1)

隐私数据(1)

markdown(1)

html(1)

crate(1)

async(1)

异步(1)

旅游(1)

不忘生活(1)

葛仙山(1)

hpy(1)

python-扩展(1)

正则表达式(1)

解析器组合因子(1)

组合器(1)

regular-expression(1)

parser-combinator(1)

regex(1)

官方更新(1)

rust-工作招聘(1)

rust-技术资料(1)

rust-周最佳-crate(1)

rust-web-框架(1)

rust-web-framework(1)

rust-核心团队(1)

rust-core-team(1)

rust-language-team(1)

pyo3(1)

rust-python-集成(1)

python-性能改进(1)

迭代器(1)

iterator-trait(1)

国际象棋(1)

chess(1)

游戏引擎(1)

game-engine(1)

虚拟化(1)

unikernel(1)

rustyhermit(1)

linux(1)

virtualization(1)

sandboxing(1)

沙箱技术(1)

数据采集(1)

异步数据采集(1)

docker(1)

镜像(1)

生产环境(1)

rust-评价(1)

rust-2021-edition(1)

rust-2021-版本(1)

graphql-查询(1)

vision-doc(1)

愿景文档(1)

代码重构(1)

steering-cycle(1)

方向周期(1)

隐私声明(1)

机器人(1)

robotics(1)

rustdoc(1)

rust-编译器(1)

实时音频(1)

real-time-audio(1)

变更服务(1)

mutation(1)

查询服务(1)

query(1)

rust-贡献者(1)

rust-轶事(1)

rust-稳定版(1)

rust-预发布(1)

rust-测试(1)

安全编程(1)

可信计算(1)

安全代码(1)

secure-code(1)

rust-android-integrate(1)

rust-embedded(1)

rust-嵌入式(1)

rust-生产环境(1)

rust-production(1)

网页快照(1)

网页截图(1)

水印效果(1)

图片水印(1)

yew-router(1)

css(1)

web-前端(1)

wasm-bindgen(1)

区块链(1)

blockchain(1)

dotenv(1)

标识符(1)

rust-1.53.0(1)

rust-1.56.0(1)

rust-项目升级(1)

异步运行时(1)

ssr(1)

tokio(1)

warp(1)

reqwest(1)

graphql-rust(1)


Elsewhere

- Open Source
  1. github/zzy
  2. github/sansx
- Learning & Studying
  1. Rust 学习资料 - 泥芹