选择

好了,我们用pushpop做。不瞒你说,我在那里有点情绪化了。编译时的正确性是 一种可怕的毒药。

让我们做一些简单的事情来冷静一下:让我们实现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

好吧,我只是在烧我的电脑。

这与我们的单链栈的逻辑完全相同。为什么事情会不同。为什么。

答案其实就是本章的全部寓意。RefCells使一切变得悲伤。到现在为止,RefCells只是一个讨 厌的东西。现在它们将成为一场噩梦。

那么发生了什么?为了理解这一点,我们需要回到borrow的定义上:

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

在布局部分,我们说:

RefCell不是静态地执行这些规则,而是在运行时执行它们。 如果你违反了这些规则,RefCell就会惊慌失措,使程序崩溃。 为什么它会返回这些Ref和RefMut的东西?好吧,它们的行为基本上和Rcs一样,只是用于借用。 另外,它们保持RefCell的借用,直到它们超出范围。我们稍后会讨论这个问题。

现在就是那个稍后。

RefRefMut分别实现了DerefDerefMut。因此,就大多数意图和目的而言,它们 的行为与&T&mut T完全一样。然而,由于这些特性的工作方式,被返回的引用与引用 的生命周期有关,而不是与实际的RefCell有关。这意味着,只要我们保留引用,Ref就必须一直 存在。

事实上,这对于正确性是必要的。当一个Ref被放弃时,它告诉RefCell它不再被借用。因此,如 果我们确实设法保持我们的引用比引用存在的时间长,我们就可以在引用四处游荡时得到一个 RefMut,并完全破坏Rust的类型系统的一半。

那么,这让我们怎么办呢?我们只想返回一个引用,但我们需要保持这个引用的存在。但是一旦 我们从peek中返回引用,函数就结束了,而Ref就出了范围。

😖

据我所知,我们在这里实际上已经完全陷入了困境。你不能像这样把RefCells的使用完全封装起来。

但是......如果我们放弃了完全隐藏我们的实现细节呢?如果我们返回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,你也可以映射一个Ref。

我相信某些人真的很兴奋,因为单体或其他什么,但我并不关心这些。我也不认为这是一个合 适的单体,因为没有None-like的情况,但我离题了。

它很酷,这对我来说才是最重要的。我需要这个

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

啊耶。

让我们通过从我们的堆栈中插入测试来确保它的工作。我们需要做一些处理,以应对Refs不实 现比较的事实。

#[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

棒!