52、Iterators(迭代器)
Iteration(迭代)
在第一个练习中,我们学到Rust
允许我们使用for
循环遍历集合。当时我们关注的是范围(例如0..5),但是对于数组和vector等集合也是如此。
// It works for `Vec`s
let v = vec![1, 2, 3];
for n in v {
println!("{}", n);
}
// It also works for arrays
let a: [u32; 3] = [1, 2, 3];
for n in a {
println!("{}", n);
}
是时候去学习for循环的底层机制了。
for
desugaring(脱糖)
每次在Rust中编写for
循环时,编译器都会将其解析为以下代码:
let mut iter = IntoIterator::into_iter(v);
loop {
match iter.next() {
Some(n) => {
println!("{}", n);
}
None => break,
}
}
loop
是另一个循环结构,位于for
和while
之上。loop
块将永远执行,除非我们明确的使用break
退出循环。
Iterator
trait
上面的代码中的next
方法来自Iterator
trait。Iterator
trait在Rust的标准库中定义,并为可以生成一系列值得类型提供了一个共享接口:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Item
关联类型指定了迭代器产生的值的类型。
next
返回序列中的下一个值。
如果要返回的值存在,那么就返回Some(value)
,如果没有要返回的类型,他就会返回None
。
注意:当迭代器返回 None
时,并不能保证该迭代器已被耗尽。只有当迭代器实现了(更严格的)FusedIterator
trait 时,才确保迭代器在返回 None
后不会再产生任何值。
补充:“迭代器的耗尽”(exhausted)是指迭代器已经遍历完所有可提供的元素,之后调用 next()
方法将永远返回 None
,不会再产生任何新的值。
IntoIterator
trait
并非所有类型都实现了Iterator
,但许多类型都可以转换为实现了Iterator
的类型。
这就是IntoIterator
的用武之地:
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
into_iter
方法会消耗原始值,并返回一个遍历其元素的迭代器。
一个类型只能有一个IntoIterator
的实现:对于for
应该脱糖到什么程度,不能有歧义。
一个细节:每个实现了Iterator
的类型也会自动实现IntoIterator
。它们只是在 into_iter
中直接返回自身。
边界检查(Bounds checks)
迭代迭代器有一个好的副作用:根据设计,我们不能越界。
这使得Rust能够从生成的机器代码里移除边界检查,从而加快迭代速度。
换句话说,
let v = vec![1, 2, 3];
for n in v {
println!("{}", n);
}
通常比下面的代码要快:
let v = vec![1, 2, 3];
for i in 0..v.len() {
println!("{}", v[i]);
}
这项规则也有例外:编译器有时可以证明即使使用手动索引也不会越界,因此无论如何都会删除边界检查。但一般来讲,尽可能倾向于迭代器迭代,而不是索引。
二者区别
核心一句话:
Iterator
是用来遍历一个已经存在的迭代器的IntoIterator
是用来将某个集合类型转换为一个迭代器的
比如vector原生支持Iterator
,但是我们自定义的类型TicketStore
里面装着的是vector
,实现IntoIterator
就相当于可以直接迭代TicketStore
。简单来说就是迭代不需要专门拆开获取里面的可迭代实例了。Into
进去的意思,IntoIterator
,不就是进去迭代的意思吗(
练习
这次的练习,是让我们为TicketStore
结构体实现IntoIterator
,从而遍历里面的Ticket实例。先看题目:
use ticket_fields::{TicketDescription, TicketTitle};
// TODO: Let's start sketching our ticket store!
// First task: implement `IntoIterator` on `TicketStore` to allow iterating over all the tickets
// it contains using a `for` loop.
// TODO:让我们开始设计我们的票务存储系统!
// 第一个任务:为 TicketStore 实现 IntoIterator,以便能够使用 for 循环遍历它所包含的所有票据。
//
// Hint: you shouldn't have to implement the `Iterator` trait in this case.
// You want to *delegate* the iteration to the `Vec<Ticket>` field in `TicketStore`.
// Look at the standard library documentation for `Vec` to find the right type
// to return from `into_iter`.
// 提示:在这种情况下,你不需要实现 Iterator trait。
// 你应该将迭代行为“委托”给 TicketStore 中的 Vec<Ticket> 字段。
// 查看标准库中 Vec 的文档,找出 into_iter 应该返回的正确类型。
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
impl Ticket {
pub fn new(title: TicketTitle, description: TicketDescription, status: Status) -> Self {
Self {
title,
description,
status,
}
}
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
pub fn new() -> Self {
Self {
tickets: Vec::new(),
}
}
pub fn add_ticket(&mut self, ticket: Ticket) {
self.tickets.push(ticket);
}
pub fn tickets(&self) -> &Vec<Ticket> {
&self.tickets
}
}
i/* TODO */
其实也并不难,我们为trait实现方法也不止一次了,这个RustRover也很聪明,敲着敲着就敲出来了:
impl IntoIterator for TicketStore {
type Item = Ticket;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.tickets.into_iter()
}
}
这一节的知识点部分其实也讲了。
那么这一节的学习就先到这里。