2.3 Peek

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

上次我们甚至懒得实现 peek。现在来做一下。只需要在链表头存在元素时返回对它的引用。听起来简单,试试:

1
2
3
4
5
pub fn peek(&self) -> Option<&T> {
    self.head.map(|node| {
        &node.elem
    })
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> cargo build

error[E0515]: cannot return reference to local data `node.elem`
  --> src/second.rs:37:13
   |
37 |             &node.elem
   |             ^^^^^^^^^^ returns a reference to data owned by the current function

error[E0507]: cannot move out of borrowed content
  --> src/second.rs:36:9
   |
36 |         self.head.map(|node| {
   |         ^^^^^^^^^ cannot move out of borrowed content

唉。Rust,现在又怎么了?

map 按值获取 self,会把 Option 从它所在的位置移走。以前没问题,因为我们刚 take 出来;但现在我们其实想让它留在原地。处理这个问题的正确做法是用 Option 的 as_ref 方法,定义如下:

1
2
3
impl<T> Option<T> {
    pub fn as_ref(&self) -> Option<&T>;
}

它把 Option<T> 降级为指向其内部内容的引用 Option。我们自己用显式 match 也能做,但呃不要。这意味着要多解引用一层才能穿过额外的间接层,好在 . 运算符会帮我们处理。

1
2
3
4
5
pub fn peek(&self) -> Option<&T> {
    self.head.as_ref().map(|node| {
        &node.elem
    })
}
1
2
3
cargo build

    Finished dev [unoptimized + debuginfo] target(s) in 0.32s

搞定了。

用 as_mut 还可以做可变版本:

1
2
3
4
5
pub fn peek_mut(&mut self) -> Option<&mut T> {
    self.head.as_mut().map(|node| {
        &mut node.elem
    })
}
1
> cargo build

轻松。

别忘了测试:

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

    assert_eq!(list.peek(), Some(&3));
    assert_eq!(list.peek_mut(), Some(&mut 3));
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 3 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::peek ... ok

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

不错,但我们并没有真正测试能否通过 peek_mut 的返回值做修改,对吧?如果引用是可变的但没人去改它,我们真的测过可变性吗?试试对这个 Option<&mut T> 用 map 写入一个深刻的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#[test]
fn peek() {
    let mut list = List::new();
    assert_eq!(list.peek(), None);
    assert_eq!(list.peek_mut(), None);
    list.push(1); list.push(2); list.push(3);

    assert_eq!(list.peek(), Some(&3));
    assert_eq!(list.peek_mut(), Some(&mut 3));
    list.peek_mut().map(|&mut value| {
        value = 42
    });

    assert_eq!(list.peek(), Some(&42));
    assert_eq!(list.pop(), Some(42));
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> cargo test

error[E0384]: cannot assign twice to immutable variable `value`
   --> src/second.rs:100:13
    |
99  |         list.peek_mut().map(|&mut value| {
    |                                   -----
    |                                   |
    |                                   first assignment to `value`
    |                                   help: make this binding mutable: `mut value`
100 |             value = 42
    |             ^^^^^^^^^^ cannot assign twice to immutable variable          ^~~~~

编译器抱怨 value 不可变,但我们明明写了 &mut value;怎么回事?原来那样写闭包参数并不是指定 value 为可变引用,而是创建一个与闭包参数匹配的模式;|&mut value| 的意思是「参数是可变引用,但请把指向的值复制到 value 里」。如果用 |value|,value 的类型就是 &mut i32,我们才能真正修改头节点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    #[test]
    fn peek() {
        let mut list = List::new();
        assert_eq!(list.peek(), None);
        assert_eq!(list.peek_mut(), None);
        list.push(1); list.push(2); list.push(3);

        assert_eq!(list.peek(), Some(&3));
        assert_eq!(list.peek_mut(), Some(&mut 3));

        list.peek_mut().map(|value| {
            *value = 42
        });

        assert_eq!(list.peek(), Some(&42));
        assert_eq!(list.pop(), Some(42));
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 3 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::peek ... ok

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

好多了!

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