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是另一个循环结构,位于forwhile之上。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()
    }
}

这一节的知识点部分其实也讲了。

 

那么这一节的学习就先到这里。

阅读剩余
THE END