Peek

有一件事我们上次甚至没有去做,那就是 peek。我们开始吧,我们需要做的就是返回对列表头元素的引用(如果它存在的话)。听起来很简单,让我们试试:

pub fn peek(&self) -> Option<&T> {
    self.head.map(|node| {
        &node.elem
    })
}
> 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从它所在的对象中移出。以前这是好的,因为我们刚刚把它拿出来,但现在我们真的想把它留在原来的地方。正确的处理方法是使用 Option 上的 as_ref 方法,它有以下定义:

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

它将 Option 降级为对其内部的引用。我们可以自己用一个显式的匹配来完成,但是不可以。这确实意味着我们需要做一个额外的解引用来切断额外的间接寻址,但谢天谢地,operator 为我们处理。

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

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

搞定了。

我们也可以使用 as_mut 创建一个可变的版本:

pub fn peek_mut(&mut self) -> Option<&mut T> {
    self.head.as_mut().map(|node| {
        &mut node.elem
    })
}
lists::cargo build

别忘了测试一下:

#[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));
}
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的返回值,是吗?如果一个引用是可变的,但是没有人对它进行变异,那么我们真的测试过它的可变性吗?让我们试着在这个选项上使用map,在以下位置输入一个深刻的值:

#[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));
}
> 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          ^~~~~

编译器抱怨值是不可变的,但是我们很清楚地写下了&mut value;给出了什么?事实证明,以这种方式编写闭包的参数并没有指定值是可变引用。相反,它会创建一个模式,该模式将与闭包的参数相匹配;|&mut value |意味着“参数是一个可变的引用,但是请将它指向的值复制到value中”。如果我们只使用 |value |,那么值的类型将是&mut i32,我们实际上可以改变head:

#[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));
}
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

好多了!

Last updated