4.4 Peek
原文链接: https://rust-unofficial.github.io/too-many-lists/fourth-peek.html
好了,我们挺过了 push 和 pop。不骗你,中间有点情绪波动。编译期正确性真是令人上瘾。
我们来冷静一下,做点简单的:实现 peek_front。以前这总是很容易。现在应该还是很容易,对吧?
对吧?
事实上,我觉得可以直接复制粘贴!
1
2
3
4
5
| pub fn peek_front(&self) -> Option<&T> {
self.head.as_ref().map(|node| {
&node.elem
})
}
|
等等。这次不行。
1
2
3
4
5
6
| pub fn peek_front(&self) -> Option<&T> {
self.head.as_ref().map(|node| {
// 借用!!!!
&node.borrow().elem
})
}
|
哈。
1
2
3
4
5
6
7
8
9
10
11
| cargo build
error[E0515]: cannot return value referencing temporary value
--> src/fourth.rs:66:13
|
66 | &node.borrow().elem
| ^ ----------^^^^^
| | |
| | temporary value created here
| |
| returns a value referencing data owned by the current function
|
好吧,我要把电脑烧了。
这和我们的单链表栈是完全一样的逻辑。为什么情况不同了。为什么。
答案其实就是本章的核心教训:RefCell 让一切都变得悲伤。到目前为止,RefCell 只是烦人。现在它们要变成噩梦了。
那么发生了什么?要理解这一点,我们需要回到 borrow 的定义:
1
2
| fn borrow<'a>(&'a self) -> Ref<'a, T>
fn borrow_mut<'a>(&'a self) -> RefMut<'a, T>
|
在布局那一节我们说过:
Rather than enforcing this statically, RefCell enforces them at runtime.
If you break the rules, RefCell will just panic and crash the program.
Why does it return these Ref and RefMut things? Well, they basically behave
like Rcs but for borrowing. Also they keep the RefCell borrowed until they go out
of scope. We’ll get to that later.
现在就是"稍后"了。
Ref 和 RefMut 分别实现了 Deref 和 DerefMut。所以在大多数意图和目的上,它们的行为完全像 &T 和 &mut T。然而,由于这些 trait 的工作方式,返回的引用的生命周期与 Ref 绑定,而不是与实际的 RefCell 绑定。这意味着只要我们还持有那个引用,Ref 就必须一直存在。
这实际上对正确性是必要的。当 Ref 被 drop 时,它会告诉 RefCell 不再被借用了。所以如果我们真的设法让引用比 Ref 存活得更久,我们可能在还有引用存在时获得 RefMut,从而彻底破坏 Rust 的类型系统。
那么我们该怎么办?我们只想返回一个引用,但我们需要让这个 Ref 一直存在。可一旦我们从 peek 返回引用,函数就结束了,Ref 也会离开作用域。
😖
据我所知,我们在这里实际上完全无路可走。你无法那样完全封装 RefCell 的使用。
但是……如果我们放弃完全隐藏实现细节呢?如果我们返回 Ref 呢?
1
2
3
4
5
| pub fn peek_front(&self) -> Option<Ref<T>> {
self.head.as_ref().map(|node| {
node.borrow()
})
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| > cargo build
error[E0412]: cannot find type `Ref` in this scope
--> src/fourth.rs:63:40
|
63 | pub fn peek_front(&self) -> Option<Ref<T>> {
| ^^^ not found in this scope
help: possible candidates are found in other modules, you can import them into scope
|
1 | use core::cell::Ref;
|
1 | use std::cell::Ref;
|
|
呃。得导入一些东西。
1
| use std::cell::{Ref, RefCell};
|
1
2
3
4
5
6
7
8
9
10
11
12
| > cargo build
error[E0308]: mismatched types
--> src/fourth.rs:64:9
|
64 | / self.head.as_ref().map(|node| {
65 | | node.borrow()
66 | | })
| |__________^ expected type parameter, found struct `fourth::Node`
|
= note: expected type `std::option::Option<std::cell::Ref<'_, T>>`
found type `std::option::Option<std::cell::Ref<'_, fourth::Node<T>>>`
|
嗯……对了。我们有的是 Ref<Node<T>>,但我们想要 Ref<T>。我们可以放弃所有封装希望直接返回那个。我们也可以让事情更复杂,把 Ref<Node<T>> 包在一个新类型里,只暴露对 &T 的访问。
这两个选项都有点逊。
相反,我们要更深入。来点乐子。我们的乐子来源是这货:
1
2
3
| map<U, F>(orig: Ref<'b, T>, f: F) -> Ref<'b, U>
where F: FnOnce(&T) -> &U,
U: ?Sized
|
Make a new Ref for a component of the borrowed data.
没错:就像你可以对 Option 做 map 一样,你也可以对 Ref 做 map。
我敢肯定某处有人因为单子什么的很兴奋,但我不在乎那些。而且我不认为它是真正的单子,因为没有类似 None 的情况,不过跑题了。
它很酷,这就够了。我需要这个。
1
2
3
4
5
| pub fn peek_front(&self) -> Option<Ref<T>> {
self.head.as_ref().map(|node| {
Ref::map(node.borrow(), |node| &node.elem)
})
}
|
噢耶
我们来确认一下这能工作,把栈的测试改一改。我们得处理 Ref 不能比较的问题。
1
2
3
4
5
6
7
8
| #[test]
fn peek() {
let mut list = List::new();
assert!(list.peek_front().is_none());
list.push_front(1); list.push_front(2); list.push_front(3);
assert_eq!(&*list.peek_front().unwrap(), &3);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| > cargo test
Running target/debug/lists-5c71138492ad4b4a
running 10 tests
test first::test::basics ... ok
test fourth::test::basics ... ok
test second::test::basics ... ok
test fourth::test::peek ... ok
test second::test::iter_mut ... ok
test second::test::into_iter ... ok
test third::test::basics ... ok
test second::test::peek ... ok
test second::test::iter ... ok
test third::test::iter ... ok
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured
|
太好了!