返回RefCell中Vec的迭代器

Nob*_*bbZ 8 iterator lifetime rust

鉴于以下structimpl:

use std::slice::Iter;
use std::cell::RefCell;

struct Foo {
    bar: RefCell<Vec<u32>>,
}

impl Foo {
    pub fn iter(&self) -> Iter<u32> {
        self.bar.borrow().iter()
    }
}

fn main() {}
Run Code Online (Sandbox Code Playgroud)

我收到有关终身问题的错误消息:

error: borrowed value does not live long enough
  --> src/main.rs:9:9
   |
9  |         self.bar.borrow().iter()
   |         ^^^^^^^^^^^^^^^^^ does not live long enough
10 |     }
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the body at 8:36...
  --> src/main.rs:8:37
   |
8  |       pub fn iter(&self) -> Iter<u32> {
   |  _____________________________________^ starting here...
9  | |         self.bar.borrow().iter()
10 | |     }
   | |_____^ ...ending here
Run Code Online (Sandbox Code Playgroud)

我怎么能返回并使用bars迭代器?

Vla*_*eev 12

您不能这样做,因为它允许您绕过运行时检查唯一性违规.

RefCell为您提供了一种将可变性排他性检查"推迟"到运行时的方法,作为交换,允许通过共享引用对其保存的数据进行变异.这是使用RAII保护完成的:您可以使用共享引用获取保护对象RefCell,然后RefCell使用此保护对象访问内部数据:

&'a RefCell<T>        -> Ref<'a, T> (with borrow) or RefMut<'a, T> (with borrow_mut)
&'b Ref<'a, T>        -> &'b T
&'b mut RefMut<'a, T> -> &'b mut T
Run Code Online (Sandbox Code Playgroud)

这里的关键点是,'b不同的是从'a,这允许获得&mut T而无需引用&mut到参考RefCell.但是,这些参考文献将与警卫相关联,并且不能比守卫更长寿.这是有意这样做:RefRefMut析构函数切换其内部的各种标志RefCell,迫使易变性检查和强制borrow()borrow_mut()恐慌,如果这些检查失败.

你可以做的最简单的事情是返回一个包装器Ref,一个引用将实现IntoIterator:

use std::cell::Ref;

struct VecRefWrapper<'a, T: 'a> {
    r: Ref<'a, Vec<T>>
}

impl<'a, 'b: 'a, T: 'a> IntoIterator for &'b VecRefWrapper<'a, T> {
    type IntoIter = Iter<'a, T>;
    type Item = &'a T;

    fn into_iter(self) -> Iter<'a, T> {
        self.r.iter()
    }
}
Run Code Online (Sandbox Code Playgroud)

(在操场上试试)

你无法直接实现IntoIterator,VecRefWrapper因为内部Ref将被消耗into_iter(),给你基本上与你现在相同的情况.

  • 一辈子太难了!恭喜你理解它并解释它。 (2认同)

kwa*_*ick 6

替代解决方案

这是一个替代解决方案,它按预期使用内部可变性。&T我们不应该为值创建迭代器,而应该为Ref<T>值创建一个自动引用的迭代器。

struct Iter<'a, T> {
    inner: Option<Ref<'a, [T]>>,
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = Ref<'a, T>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.inner.take() {
            Some(borrow) => match *borrow {
                [] => None,
                [_, ..] => {
                    let (head, tail) = Ref::map_split(borrow, |slice| {
                        (&slice[0], &slice[1..])
                    });
                    self.inner.replace(tail);
                    Some(head)
                }
            },
            None => None,
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

操场

解释

已接受的答案有一些重大缺点,可能会让 Rust 新手感到困惑。我将解释,根据我的个人经验,所接受的答案实际上可能对初学者有害,以及为什么我相信这种替代方案按预期使用内部可变性和迭代器。

正如前面的答案重要强调的那样,使用RefCell创建了一个发散的类型层次结构,隔离了对共享值的可变和不可变访问,但您不必担心生命周期来解决迭代问题:

  RefCell<T>       .borrow()  ->     Ref<T>      .deref()  ->       &T
  RefCell<T>   .borrow_mut()  ->  RefMut<T>  .deref_mut()  ->   &mut T
Run Code Online (Sandbox Code Playgroud)

在没有生命周期的情况下解决这个问题的关键是方法,这在书中Ref::map被严重遗漏了。“对借用数据的组件进行新引用”,或者换句话说,将外部类型的 a 转换为某个内部值的 a:Ref::mapRef<T>Ref<U>

Ref::map(Ref<T>, ...)  ->  Ref<U>
Run Code Online (Sandbox Code Playgroud)

Ref::map和它的对应物RefMut::map是内部可变性模式的真正明星,而不是 borrow()borrow_mut()

为什么?因为与borrow()and不同borrow_mut()Ref::mutandRefMut::map允许您创建对可以“返回”的内部值的引用。

考虑向问题中描述的结构添加一个first()方法:Foo

  RefCell<T>       .borrow()  ->     Ref<T>      .deref()  ->       &T
  RefCell<T>   .borrow_mut()  ->  RefMut<T>  .deref_mut()  ->   &mut T
Run Code Online (Sandbox Code Playgroud)

不,.borrow()创建一个Ref仅在方法返回之前有效的临时对象:

error[E0515]: cannot return value referencing temporary value
 --> src/main.rs:9:11
  |
9 |           &self.bar.borrow()[0]
  |           ^-----------------^^^
  |           ||
  |           |temporary value created here
  |           returns a value referencing data owned by the current function

error: aborting due to previous error; 1 warning emitted
Run Code Online (Sandbox Code Playgroud)

如果我们将其分解并使隐含的尊重变得明确,我们可以使正在发生的事情变得更加明显:

fn first(&self) -> &u32 {
    let borrow: Ref<_> = self.bar.borrow();
    let bar: &Vec<u32> = borrow.deref();
    &bar[0]
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以看到.borrow()创建了一个Ref<T>由方法范围拥有的 ,并且不会返回,因此甚至在可以使用它提供的引用之前就被删除了。因此,我们真正需要的是返回一个拥有的类型而不是引用。我们想要返回一个Ref<T>,因为它Deref为我们实现了!

Ref::map将帮助我们对组件(内部)值执行此操作:

fn first(&self) -> Ref<u32> {
    Ref::map(self.bar.borrow(), |bar| &bar[0])
}
Run Code Online (Sandbox Code Playgroud)

当然,这.deref()仍然会自动发生,并且Ref<u32>大部分是引用透明的,如&u32

明白了。使用时容易犯的一个错误Ref::map是尝试在闭包中创建一个拥有的值,而当我们尝试使用 时这是不可能的borrow()。考虑第二个参数的类型签名,即函数:FnOnce(&T) -> &U,。它返回一个引用,而不是一个拥有的类型!

这就是为什么我们在答案中使用切片&v[..]而不是尝试使用向量的.iter()方法,该方法返回一个拥有的std::slice::Iter<'a, T>。切片是一种参考类型。

额外的想法

好吧,现在我将尝试证明为什么这个解决方案比公认的答案更好。

首先, 的使用IntoIterator与 Rust 标准库不一致,并且可以说与该特征的目的和意图不一致。Trait 方法消耗selffn into_iter(self) -> ....

Ref::map(Ref<T>, ...)  ->  Ref<U>
Run Code Online (Sandbox Code Playgroud)

间接使用IntoIterator包装器是不一致的,因为您使用包装器而不是集合。根据我的经验,初学者将从坚持惯例中受益。我们应该使用常规的Iterator.

接下来,该IntoIterator特征是为引用&VecRefWrapper而不是拥有的类型实现的VecRefWrapper

假设您正在实现一个库。API 的使用者必须使用引用运算符看似任意地修饰拥有的值,如操场上的示例所示:

fn first(&self) -> &u32 {
      &self.bar.borrow()[0]
}
Run Code Online (Sandbox Code Playgroud)

如果您是 Rust 新手,这是一个微妙且令人困惑的区别。当该值由循环范围匿名拥有且仅应存在于循环范围内时,为什么我们必须引用该值?

最后,上面的解决方案展示了如何通过内部可变性钻取所有数据,并使实现可变迭代器的前进道路变得清晰。使用RefMut