37-迭代器

迭代器 — Easy Rust 中文译本

译文 · 基于 Easy Rust

原文链接: https://dhghomon.github.io/easy_rust/Chapter_36.html

中文参考:kumakichi/easy_rust_chs

迭代器

迭代器是一个构造,它可以给你集合中的元素,一次一个。实际上,我们已经使用了很多迭代器:for循环给你一个迭代器。当你想在其他时候使用迭代器时,你必须选择什么样的迭代器:

  • .iter() 引用的迭代器
  • .iter_mut() 可变引用的迭代器
  • .into_iter() 值的迭代器(不是引用)

for循环其实只是一个拥有值的迭代器。这就是为什么可以让它变得可变,然后你可以在使用的时候改变值。

我们可以这样使用迭代器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let vector1 = vec![1, 2, 3]; // 我们会对这个用 .iter() 和 .into_iter()
    let vector1_a = vector1.iter().map(|x| x + 1).collect::<Vec<i32>>();
    let vector1_b = vector1.into_iter().map(|x| x * 10).collect::<Vec<i32>>();

    let mut vector2 = vec![10, 20, 30]; // 我们会对这个用 .iter_mut()
    vector2.iter_mut().for_each(|x| *x +=100);

    println!("{:?}", vector1_a);
    println!("{:?}", vector2);
    println!("{:?}", vector1_b);
}

这个将打印:

1
2
3
[2, 3, 4]
[110, 120, 130]
[10, 20, 30]

前两个我们用了一个叫.map()的方法。这个方法可以让你对每一个元素做一些事情,然后把它传递下去。最后我们用的是一个叫.for_each()的方法。这个方法只是让你对每一个元素做一些事情。.iter_mut()加上for_each()基本上就是一个for的循环。在每一个方法里面,我们可以给每一个元素起一个名字(我们刚才叫它 x),然后用它来改变它。这些被称为闭包,我们将在下一节学习它们。

让我们再来看看它们,一次一个。

首先我们用.iter()对vector1进行引用。我们给每个元素都加了1,并使其成为一个新的Vec。vector1还活着,因为我们只用了引用:我们没有按值取。现在我们有 vector1,还有一个新的 Vec 叫 vector1_a。因为.map()只是传递了它,所以我们需要使用.collect()把它变成一个Vec。

然后我们用into_iter从vector1中按值得到一个迭代器。这样就破坏了vector1,因为这就是into_iter()的作用。所以我们做了vector1_b之后,就不能再使用vector1了。

最后我们在vector2上使用.iter_mut()。它是可变的,所以我们不需要使用.collect()来创建一个新的Vec。相反,我们用可变引用改变同一Vec中的值。所以vector2仍然存在。因为我们不需要一个新的Vec,我们使用for_each:它就像一个for循环。

迭代器如何工作

迭代器的工作原理是使用一个叫做 .next() 的方法,它给出一个 Option。当你使用迭代器时,Rust会一遍又一遍地调用next()。如果得到 Some,它就会继续前进。如果得到 None,它就停止。

你还记得 assert_eq! 宏吗?在文档中,你经常看到它。这里它展示了迭代器的工作原理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let my_vec = vec!['a', 'b', '거', '柳']; // 只是普通的 Vec

    let mut my_vec_iter = my_vec.iter(); // 现在是 Iterator 类型,但还没调用

    assert_eq!(my_vec_iter.next(), Some(&'a'));  // 用 .next() 取第一项
    assert_eq!(my_vec_iter.next(), Some(&'b'));  // 再取下一项
    assert_eq!(my_vec_iter.next(), Some(&'거')); // 再来一次
    assert_eq!(my_vec_iter.next(), Some(&'柳')); // 再来一次
    assert_eq!(my_vec_iter.next(), None);        // 没有了:只剩 None
    assert_eq!(my_vec_iter.next(), None);        // 还可以继续调用 .next(),但永远是 None
}

为自己的struct或enum实现Iterator并不难。首先我们创建一个书库,想一想。

 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
33
34
35
#[derive(Debug)] // 想用 {:?} 打印
struct Library {
    library_type: LibraryType, // 这是我们的枚举
    books: Vec<String>, // 书单
}

#[derive(Debug)]
enum LibraryType { // 图书馆可以是城市或乡村图书馆
    City,
    Country,
}

impl Library {
    fn add_book(&mut self, book: &str) { // 用 add_book 添加新书
        self.books.push(book.to_string()); // 接受 &str,转成 String,再加入 Vec
    }

    fn new() -> Self { // 创建一个新 Library
        Self {
            library_type: LibraryType::City, // 多数在城市,所以选 City
                                             // 大多数时候
            books: Vec::new(),
        }
    }
}

fn main() {
    let mut my_library = Library::new(); // 新建一个图书馆
    my_library.add_book("The Doom of the Darksword"); // 加几本书
    my_library.add_book("Demian - die Geschichte einer Jugend");
    my_library.add_book("구운몽");
    my_library.add_book("吾輩は猫である");

    println!("{:?}", my_library.books); // 可以打印书单
}

这很好用。现在我们想为库实现Iterator,这样我们就可以在for循环中使用它。现在如果我们尝试 for 循环,它就无法工作。

1
2
3
for item in my_library {
    println!("{}", item); // ⚠️
}

它说:

1
2
3
4
5
6
7
8
error[E0277]: `Library` is not an iterator
  --> src\main.rs:47:16
   |
47 |    for item in my_library {
   |                ^^^^^^^^^^ `Library` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `Library`
   = note: required by `std::iter::IntoIterator::into_iter`

但是我们可以用impl Iterator for Library把库做成迭代器。Iteratortrait的信息在标准库中。https://doc.rust-lang.org/std/iter/trait.Iterator.html

在页面的左上方写着:Associated Types: Item和Required Methods: next。“关联类型"的意思是 “一起使用的类型”。我们的关联类型将是String,因为我们希望迭代器给我们提供String。

在页面中,它有一个看起来像这样的例子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在 Some 与 None 之间交替的迭代器
struct Alternate {
    state: i32,
}

impl Iterator for Alternate {
    type Item = i32;

    fn next(&mut self) -> Option<i32> {
        let val = self.state;
        self.state = self.state + 1;

        // 偶数则 Some(i32),否则 None
        if val % 2 == 0 {
            Some(val)
        } else {
            None
        }
    }
}

fn main() {}

你可以看到impl Iterator for Alternate下面写着type Item = i32。这就是关联类型。我们的迭代器将针对我们的书籍列表,这是一个Vec<String>。当我们调用next的时候。 它将给我们一个String。所以我们就写type Item = String;。这就是关联项。

为了实现 Iterator,你需要写 fn next() 函数。这是你决定迭代器应该做什么的地方。对于我们的 Library,我们首先希望它给我们最后一本书。所以我们将match与.pop()一起,如果是Some的话,就把最后一项去掉。我们还想为每个元素打印 “is found!"。现在它看起来像这样:

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#[derive(Debug, Clone)]
struct Library {
    library_type: LibraryType,
    books: Vec<String>,
}

#[derive(Debug, Clone)]
enum LibraryType {
    City,
    Country,
}

impl Library {
    fn add_book(&mut self, book: &str) {
        self.books.push(book.to_string());
    }

    fn new() -> Self {
        Self {
            library_type: LibraryType::City,
            // 大多数时候
            books: Vec::new(),
        }
    }
}

impl Iterator for Library {
    type Item = String;

    fn next(&mut self) -> Option<String> {
        match self.books.pop() {
            Some(book) => Some(book + " is found!"), // Rust 允许 String + &str
            None => None,
        }
    }
}

fn main() {
    let mut my_library = Library::new();
    my_library.add_book("The Doom of the Darksword");
    my_library.add_book("Demian - die Geschichte einer Jugend");
    my_library.add_book("구운몽");
    my_library.add_book("吾輩は猫である");

    for item in my_library.clone() { // 现在可以用 for 循环了。给它一个 clone,这样 Library 不会被销毁
        println!("{}", item);
    }
}

这个打印:

1
2
3
4
吾輩は猫である is found!
구운몽 is found!
Demian - die Geschichte einer Jugend is found!
The Doom of the Darksword is found!
最后修改 August 21, 2026: 更新 (76fc81a2e)