2.5 Iter

原文链接: https://rust-unofficial.github.io/too-many-lists/second-iter.html

好,来实现 Iter。这次不能指望 List 提供我们想要的一切,得自己动手。基本思路是保存指向「下一个要 yield 的节点」的指针。因为该节点可能不存在(链表为空或迭代已结束),这个引用应该是 Option。yield 一个元素后,要前进到当前节点的 next 节点。

试试看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
pub struct Iter<T> {
    next: Option<&Node<T>>,
}

impl<T> List<T> {
    pub fn iter(&self) -> Iter<T> {
        Iter { next: self.head.map(|node| &node) }
    }
}

impl<T> Iterator for Iter<T> {
    type Item = &T;

    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.map(|node| &node);
            &node.elem
        })
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> cargo build

error[E0106]: missing lifetime specifier
  --> src/second.rs:72:18
   |
72 |     next: Option<&Node<T>>,
   |                  ^ expected lifetime parameter

error[E0106]: missing lifetime specifier
  --> src/second.rs:82:17
   |
82 |     type Item = &T;
   |                 ^ expected lifetime parameter

天哪。生命周期。听说过这东西,据说是噩梦。

试试新办法:看到那个 error[E0106] 了吗?那是编译器错误代码。可以用 rustc --explain 来查:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
> rustc --explain E0106
This error indicates that a lifetime is missing from a type. If it is an error
inside a function signature, the problem may be with failing to adhere to the
lifetime elision rules (see below).

Here are some simple examples of where you'll run into this error:

struct Foo { x: &bool }        // error
struct Foo<'a> { x: &'a bool } // correct

enum Bar { A(u8), B(&bool), }        // error
enum Bar<'a> { A(u8), B(&'a bool), } // correct

type MyStr = &str;        // error
type MyStr<'a> = &'a str; //correct
...

呃……这并没解释多少(这些文档假设我们对 Rust 的理解比现在更深)。但看起来应该给结构体加上那些 'a?试试:

1
2
3
pub struct Iter<'a, T> {
    next: Option<&'a Node<T>>,
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> cargo build

error[E0106]: missing lifetime specifier
  --> src/second.rs:83:22
   |
83 | impl<T> Iterator for Iter<T> {
   |                      ^^^^^^^ expected lifetime parameter

error[E0106]: missing lifetime specifier
  --> src/second.rs:84:17
   |
84 |     type Item = &T;
   |                 ^ expected lifetime parameter

error: aborting due to 2 previous errors

开始看出规律了……凡能加的地方都加上这些小东西:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
pub struct Iter<'a, T> {
    next: Option<&'a Node<T>>,
}

impl<'a, T> List<T> {
    pub fn iter(&'a self) -> Iter<'a, T> {
        Iter { next: self.head.map(|node| &'a node) }
    }
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;
    fn next(&'a mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.map(|node| &'a node);
            &'a node.elem
        })
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
> cargo build

error: expected `:`, found `node`
  --> src/second.rs:77:47
   |
77 |         Iter { next: self.head.map(|node| &'a node) }
   |         ---- while parsing this struct        ^^^^ expected `:`

error: expected `:`, found `node`
  --> src/second.rs:85:50
   |
85 |             self.next = node.next.map(|node| &'a node);
   |                                                  ^^^^ expected `:`

error[E0063]: missing field `next` in initializer of `second::Iter<'_, _>`
  --> src/second.rs:77:9
   |
77 |         Iter { next: self.head.map(|node| &'a node) }
   |         ^^^^ missing `next`

天哪。我们把 Rust 搞坏了。

也许该弄明白 'a 生命周期到底是什么意思。

生命周期会吓跑很多人,因为它改变了我们从编程诞生之初就熟悉的东西。到目前为止我们其实一直在躲避生命周期,尽管它们其实贯穿了我们写的所有程序。

在带垃圾回收的语言里不需要生命周期,因为 GC 会神奇地保证一切活得足够久。Rust 里大多数数据是手动管理的,所以需要别的方案。C 和 C++ 清楚地展示了如果随便让人拿栈上数据的指针会发生什么:无处不在、难以管控的不安全。大致可分两类错误:

  • 持有已出作用域之物的指针
  • 持有已被变异掉之物的指针

生命周期解决这两类问题,而且 99% 的情况下完全透明。

那生命周期是什么?

简单说,生命周期就是程序中某段代码区域(约等于块/作用域)的名字。就这样。引用带上生命周期,就是说它必须在整个区域内有效。不同事物对引用必须/能够存活多久有不同要求。整个生命周期系统本质上是一个约束求解系统,试图最小化每个引用的区域。若成功找到满足所有约束的生命周期集合,程序就能编译!否则会报错说某物活得不够久。

在函数体内通常不能谈论生命周期,也不想谈论。编译器信息齐全,能推断所有约束以找到最短生命周期。但在类型和 API 层面,编译器没有全部信息,需要你告诉它不同生命周期之间的关系,它才能弄清你在做什么。

原则上这些生命周期也可以省略,但那样检查所有借用会变成巨大的全程序分析,产生令人费解的非局部错误。Rust 的系统意味着每个函数体可以独立做借用检查,错误应该相当局部(否则就是类型签名写错了)。

但我们以前在函数签名里写过引用,也没问题啊!那是因为有些情况太常见,Rust 会自动替你选生命周期。这就是生命周期省略。

具体来说:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 输入只有一个引用,输出必须来自该输入
fn foo(&A) -> &B; // 糖化为:
fn foo<'a>(&'a A) -> &'a B;

// 多个输入,假定彼此独立
fn foo(&A, &B, &C); // 糖化为:
fn foo<'a, 'b, 'c>(&'a A, &'b B, &'c C);

// 方法:假定所有输出生命周期都来自 `self`
fn foo(&self, &B, &C) -> &D; // 糖化为:
fn foo<'a, 'b, 'c>(&'a self, &'b B, &'c C) -> &'a D;

那 fn foo<'a>(&'a A) -> &'a B 意味着什么?实际上就是说输入至少得和输出活得一样久。如果你把输出留很久,就会扩大输入必须有效的区域。一旦不再使用输出,编译器就知道输入也可以失效了。

有了这个系统,Rust 能确保没有 use-after-free,也没有在仍有未归还引用时发生变异。只要保证所有约束能成立就行!

好。那么。Iter。

先回滚到没有生命周期的状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
pub struct Iter<T> {
    next: Option<&Node<T>>,
}

impl<T> List<T> {
    pub fn iter(&self) -> Iter<T> {
        Iter { next: self.head.map(|node| &node) }
    }
}

impl<T> Iterator for Iter<T> {
    type Item = &T;
    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.map(|node| &node);
            &node.elem
        })
    }
}

我们只需要在函数和类型签名里加生命周期:

 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
// Iter 对*某个*生命周期泛型,它并不关心具体是哪个
pub struct Iter<'a, T> {
    next: Option<&'a Node<T>>,
}

// 这里没有生命周期,List 没有关联的生命周期
impl<T> List<T> {
    // 为创建 iter 的*确切*借用声明一个新的生命周期。
    // 现在 &self 必须在 Iter 存在期间一直有效。
    pub fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { next: self.head.map(|node| &node) }
    }
}

// 这里*确实*有生命周期,因为 Iter 有,我们需要定义它
impl<'a, T> Iterator for Iter<'a, T> {
    // 这里也需要,这是类型声明
    type Item = &'a T;

    // 下面都不用改,上面已经处理好了。
    // Self 继续令人振奋、精彩绝伦
    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.map(|node| &node);
            &node.elem
        })
    }
}

好,这次应该对了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cargo build

error[E0308]: mismatched types
  --> src/second.rs:77:22
   |
77 |         Iter { next: self.head.map(|node| &node) }
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `second::Node`, found struct `std::boxed::Box`
   |
   = note: expected type `std::option::Option<&second::Node<T>>`
              found type `std::option::Option<&std::boxed::Box<second::Node<T>>>`

error[E0308]: mismatched types
  --> src/second.rs:85:25
   |
85 |             self.next = node.next.map(|node| &node);
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `second::Node`, found struct `std::boxed::Box`
   |
   = note: expected type `std::option::Option<&'a second::Node<T>>`
              found type `std::option::Option<&std::boxed::Box<second::Node<T>>>`

(╯°□°)╯︵ ┻━┻

好。生命周期错误修好了,但又冒出新的类型错误。

我们想存 &Node,却得到 &Box<Node>。好办,在取引用前先解引用 Box:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
impl<T> List<T> {
    pub fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { next: self.head.map(|node| &*node) }
    }
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;
    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.map(|node| &*node);
            &node.elem
        })
    }
}
 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
cargo build
   Compiling lists v0.1.0 (/Users/ADesires/dev/temp/lists)
error[E0515]: cannot return reference to local data `*node`
  --> src/second.rs:77:43
   |
77 |         Iter { next: self.head.map(|node| &*node) }
   |                                           ^^^^^^ returns a reference to data owned by the current function

error[E0507]: cannot move out of borrowed content
  --> src/second.rs:77:22
   |
77 |         Iter { next: self.head.map(|node| &*node) }
   |                      ^^^^^^^^^ cannot move out of borrowed content

error[E0515]: cannot return reference to local data `*node`
  --> src/second.rs:85:46
   |
85 |             self.next = node.next.map(|node| &*node);
   |                                              ^^^^^^ returns a reference to data owned by the current function

error[E0507]: cannot move out of borrowed content
  --> src/second.rs:85:25
   |
85 |             self.next = node.next.map(|node| &*node);
   |                         ^^^^^^^^^ cannot move out of borrowed content

(ノಥ益ಥ)ノ ┻━┻

忘了 as_ref,于是把 Box 移进 map,会被 drop,引用就悬空了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
pub struct Iter<'a, T> {
    next: Option<&'a Node<T>>,
}

impl<T> List<T> {
    pub fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { next: self.head.as_ref().map(|node| &*node) }
    }
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.as_ref().map(|node| &*node);
            &node.elem
        })
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cargo build
   Compiling lists v0.1.0 (/Users/ADesires/dev/temp/lists)
error[E0308]: mismatched types
  --> src/second.rs:77:22
   |
77 |         Iter { next: self.head.as_ref().map(|node| &*node) }
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `second::Node`, found struct `std::boxed::Box`
   |
   = note: expected type `std::option::Option<&second::Node<T>>`
              found type `std::option::Option<&std::boxed::Box<second::Node<T>>>`

error[E0308]: mismatched types
  --> src/second.rs:85:25
   |
85 |             self.next = node.next.as_ref().map(|node| &*node);
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `second::Node`, found struct `std::boxed::Box`
   |
   = note: expected type `std::option::Option<&'a second::Node<T>>`
              found type `std::option::Option<&std::boxed::Box<second::Node<T>>>`

😭

as_ref 又加了一层间接,还得剥掉:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
pub struct Iter<'a, T> {
    next: Option<&'a Node<T>>,
}

impl<T> List<T> {
    pub fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { next: self.head.as_deref() }
    }
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.as_deref();
            &node.elem
        })
    }
}
1
cargo build

🎉 🎉 🎉

as_deref 和 as_deref_mut 自 Rust 1.40 起稳定。在此之前得写 map(|node| &**node) 和 map(|node| &mut**node)。你可能在想「哇这 &** 真别扭」,没错,但就像好酒,Rust 越陈越好,我们不必再这么写了。通常 Rust 很擅长通过解引用强制隐式做这种转换,也就是在代码里插入 * 让类型对上。能这么做是因为有借用检查器保证我们不会搞砸指针!

但这里闭包加上 Option<&T> 而不是 &T 有点复杂,编译器推不出来,得显式帮忙。好在以我经验这挺少见。

为完整起见,我们可以用turbofish给另一种提示:

1
    self.next = node.next.as_ref().map::<&Node<T>, _>(|node| &node);

看,map 是泛型函数:

1
pub fn map<U, F>(self, f: F) -> Option<U>

turbofish ::<> 让我们告诉编译器那些泛型参数应该是什么类型。这里 ::<&Node<T>, _> 表示「应返回 &Node<T>,另一个类型我不知道/不在乎」。

这样编译器就知道应对 &node 做解引用强制,不必手动加一堆 *!

但我觉得这里不算改进,只是借机展示解引用强制和有时有用的 turbofish。😅

写个测试确认没写成空操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#[test]
fn iter() {
    let mut list = List::new();
    list.push(1); list.push(2); list.push(3);

    let mut iter = list.iter();
    assert_eq!(iter.next(), Some(&3));
    assert_eq!(iter.next(), Some(&2));
    assert_eq!(iter.next(), Some(&1));
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 5 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::into_iter ... ok
test second::test::iter ... ok
test second::test::peek ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured

太棒了。

最后说明一下,这里其实可以应用生命周期省略:

1
2
3
4
5
impl<T> List<T> {
    pub fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { next: self.head.as_deref() }
    }
}

等价于:

1
2
3
4
5
impl<T> List<T> {
    pub fn iter(&self) -> Iter<T> {
        Iter { next: self.head.as_deref() }
    }
}

耶,更少的生命周期!

若不习惯「隐藏」结构体含有生命周期,可以用 Rust 2018 的「显式省略生命周期」语法 '_:

1
2
3
4
5
impl<T> List<T> {
    pub fn iter(&self) -> Iter<'_, T> {
        Iter { next: self.head.as_deref() }
    }
}
最后修改 September 19, 2026: 更新 (3489033b1)