2.1 Option

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

眼尖的读者可能已经注意到,我们其实重新发明了一个很糟糕的 Option 版本:

1
2
3
4
enum Link {
    Empty,
    More(Box<Node>),
}

Link 其实就是 Option<Box<Node>>。当然,不必到处写 Option<Box<Node>> 也挺舒服;而且不像 pop,我们并没有把它暴露给外部,所以也许还能接受。不过 Option 有一些非常好用的方法,而我们一直在手动实现。咱们别这么干了,把所有东西都换成 Option。首先,我们天真地把所有名字改成 Some 和 None:

 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
use std::mem;

pub struct List {
    head: Link,
}

// 类型别名,好耶!
type Link = Option<Box<Node>>;

struct Node {
    elem: i32,
    next: Link,
}

impl List {
    pub fn new() -> Self {
        List { head: None }
    }

    pub fn push(&mut self, elem: i32) {
        let new_node = Box::new(Node {
            elem: elem,
            next: mem::replace(&mut self.head, None),
        });

        self.head = Some(new_node);
    }

    pub fn pop(&mut self) -> Option<i32> {
        match mem::replace(&mut self.head, None) {
            None => None,
            Some(node) => {
                self.head = node.next;
                Some(node.elem)
            }
        }
    }
}

impl Drop for List {
    fn drop(&mut self) {
        let mut cur_link = mem::replace(&mut self.head, None);
        while let Some(mut boxed_node) = cur_link {
            cur_link = mem::replace(&mut boxed_node.next, None);
        }
    }
}

这稍微好一点,但真正的收益来自 Option 的方法。

首先,mem::replace(&mut option, None) 是如此常见的惯用法,Option 直接把它做成了一个方法:take。

 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
pub struct List {
    head: Link,
}

type Link = Option<Box<Node>>;

struct Node {
    elem: i32,
    next: Link,
}

impl List {
    pub fn new() -> Self {
        List { head: None }
    }

    pub fn push(&mut self, elem: i32) {
        let new_node = Box::new(Node {
            elem: elem,
            next: self.head.take(),
        });

        self.head = Some(new_node);
    }

    pub fn pop(&mut self) -> Option<i32> {
        match self.head.take() {
            None => None,
            Some(node) => {
                self.head = node.next;
                Some(node.elem)
            }
        }
    }
}

impl Drop for List {
    fn drop(&mut self) {
        let mut cur_link = self.head.take();
        while let Some(mut boxed_node) = cur_link {
            cur_link = boxed_node.next.take();
        }
    }
}

其次,match option { None => None, Some(x) => Some(y) } 也是极其常见的惯用法,它就叫 map。map 接受一个函数,对 Some(x) 里的 x 执行,得到 Some(y) 里的 y。我们可以写一个正式的 fn 传给 map,但更想内联地写要做什么。

做法是用闭包。闭包是匿名函数,还有一个额外超能力:可以引用闭包外部的局部变量!这让它们非常适合做各种条件逻辑。我们唯一做 match 的地方在 pop,那就重写它:

1
2
3
4
5
6
pub fn pop(&mut self) -> Option<i32> {
    self.head.take().map(|node| {
        self.head = node.next;
        node.elem
    })
}

啊,好多了。确认一下没弄坏什么:

1
2
3
4
5
6
7
8
9
> cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 2 tests
test first::test::basics ... ok
test second::test::basics ... ok

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

很好!接下来去真正改进代码的行为。

最后修改 August 23, 2026: 更新 (499855b16)