返回RefCell中Vec的迭代器

时间:2015-11-05 09:58:26

标签: iterator rust lifetime

鉴于以下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() {}

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

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

我如何返回并使用bar迭代器?

2 个答案:

答案 0 :(得分: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

这里的关键点是'b'a不同,&mut T允许人们在没有&mut RefCell Ref引用的情况下获得RefMut引用。但是,这些参考文献将与警卫相关联,并且不能比警卫更长寿。这是故意执行的:RefCellborrow()析构函数在borrow_mut()内切换各种标记以强制进行可变性检查,并在这些检查失败时强制RefIntoIterator恐慌

您可以做的最简单的事情是返回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() } } 周围的包装器,该引用将实现IntoIterator

VecRefWrapper

(试试on playground

您无法直接为Ref实施into_iter(),因为function get_last_chat_time(steamid){ if(!db_connect) { return 0; } chat_db.aggregate( [ {"$match":{"name":"chat-msg",steamid: steamid}}, { "$sort" : { date : -1 } }, {"$limit" : 1} ] ).toArray(function(err, list){ if (err) { console.log("Error loading history from DB: " + err); } else { var i = list.length-1; var lasttime = Math.floor((new Date() - list[i].date)/1000); console.log(Math.floor((new Date() - lasttime)/1000));//this works perfect return lasttime; } }); } console.log(get_last_chat_time(msg.steamid));// this is undefined 会消耗内部{{1}},因为您现在处于基本相同的状态

答案 1 :(得分:0)

替代解决方案

这是一个替代解决方案,它按预期使用内部可变性。我们应该为 &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,
        }
    }
}

Playground

说明

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

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

  RefCell<T>       .borrow()  ->     Ref<T>      .deref()  ->       &T
  RefCell<T>   .borrow_mut()  ->  RefMut<T>  .deref_mut()  ->   &mut T

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

Ref::map(Ref<T>, ...)  ->  Ref<U>

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

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

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

fn first(&self) -> &u32 {
      &self.bar.borrow()[0]
}

不,.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

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

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

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

Ref::map 将帮助我们为组件(内部)值做到这一点:

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

当然,.deref() 仍会自动发生,而 Ref<u32> 将主要作为 &u32 引用透明。

问题。使用 Ref::map 时容易犯的一个错误是尝试在闭包中创建一个拥有的值,这在我们尝试使用 {{1} }.考虑第二个参数的类型签名,函数:borrow()。它返回一个引用,而不是一个拥有的类型!

这就是为什么我们在答案 FnOnce(&T) -> &U, 中使用切片而不是尝试使用向量的 &v[..] 方法,该方法返回一个拥有的 .iter()。切片是一种引用类型。

其他想法

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

首先,std::slice::Iter<'a, T> 的使用与 Rust 标准库不一致,可以说是 trait 的目的和意图。 trait 方法消耗 IntoIterator: self

fn into_iter(self) -> ...

间接将 let v = vec![1,2,3,4]; let i = v.into_iter(); // v is no longer valid, it was moved into the iterator 用于包装器是不一致的,因为您使用的是包装器而不是集合。根据我的经验,初学者将受益于遵守约定。我们应该使用常规的 IntoIterator

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

假设您正在实现一个库。您的 API 的使用者将不得不使用引用操作符随意修饰拥有的值,如 Playground 上的示例所示:

VecRefWrapper

如果您不熟悉 Rust,这是一个微妙且令人困惑的区别。当它被匿名拥有并且应该只存在于循环的范围时,为什么我们必须引用该值?

最后,上面的解决方案展示了如何通过内部可变性深入到您的数据中,并使实现 mutable iterator 的前进道路也变得清晰。使用 for &i in &foo.iter() { println!("{}", i); }