Peek

好吧,我们通过了push和pop。我不想撒谎,这有点情绪化。编译时正确性是一种致命的毒药。

让我们通过做一些简单的事情来冷静一下:让我们实现 peek_front。 以前总是很容易的。 仍然很容易,对吗?

事实上,我认为可以复制粘贴它!

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

等等,这次不行。

pub fn peek_front(&self) -> Option<&T> {
    self.head.as_ref().map(|node| {
        // BORROW!!!!
        &node.borrow().elem
    })
}
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 一直是个麻烦。现在他们将成为一场噩梦。

为了理解这一点,我们需要回到借用的定义:

fn borrow<'a>(&'a self) -> Ref<'a, T>
fn borrow_mut<'a>(&'a self) -> RefMut<'a, T>

在布局部分我们说:

RefCell 不是在静态地强制执行此操作,而是在运行时强制执行它们。 如果您违反了规则,RefCell将会慌乱并使程序崩溃。 为什么返回这些Ref 和RefMut 东西? 它们的行为本质上类似于Rcs,只是为了借用。 同样,它们会保留RefCell借用,直到超出范围。 我们稍后再讨论。

这是以后的事了。

Ref 和 RefMut 分别实现了 Deref 和 DerefMut。因此,对于大多数意图和目的来说,它们的行为完全像 &t 和 &mut t。然而,由于这些特性的工作原理,返回的引用被连接到 Ref 的生存期,而不是实际的 RefCell。这意味着只要我们一直保持引用不变,就必须坐在Ref周围。

实际上,这对于正确性是必需的。 当Ref被丢弃时,它告诉RefCell它不再借用。 因此,如果我们确实设法将引用保留的时间超过了Ref的存在时间,那么当引用开始运行时,我们可以获得RefMut,并将Rust的类型系统完全破坏了一半。

那么我们该怎么办呢?我们只想返回一个引用,但我们需要保持这个 Ref 的东西。但是,只要我们从 peek 返回引用,函数就结束了,Ref 也超出了作用域。

😖

据我所知,我们实际上已经完全陷入困境了。你不能像完全封装 RefCell 那样。

但是... 如果我们放弃完全隐藏我们的实现细节呢? 如果我们返回 Refs 呢?

pub fn peek_front(&self) -> Option<Ref<T>> {
    self.head.as_ref().map(|node| {
        node.borrow()
    })
}
> 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;
   |

必须导入相关

use std::cell::{Ref, RefCell};
> 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 的访问。

这两种选择都有点站不住脚。

相反,我们要深入下去。让我们找点乐子。我们的乐趣来源于这头野兽:

map<U, F>(orig: Ref<'b, T>, f: F) -> Ref<'b, U>
    where F: FnOnce(&T) -> &U,
          U: ?Sized

为借用数据的组件创建新的引用。

是的: 就像你可以映射一个 Option,你可以映射一个引用。

我相信某个地方的某个人一定很兴奋,因为 monads 或者其他什么东西,但是我不在乎这些。我也不认为这是一个合适的 monads,因为没有类似的情况,但我离题了。

这对我来说是最重要的,我需要这个。

pub fn peek_front(&self) -> Option<Ref<T>> {
    self.head.as_ref().map(|node| {
        Ref::map(node.borrow(), |node| &node.elem)
    })
}
> cargo build

让我们通过从堆栈中调出测试来确保这是可行的。 我们需要做些调整来处理 Ref 并未实现比较的事实。

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

太棒了!

Last updated