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
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
|
好多了!