首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在RefCell中返回Vec的迭代器

在RefCell中返回Vec的迭代器
EN

Stack Overflow用户
提问于 2015-11-05 09:58:26
回答 3查看 2.4K关注 0票数 12

给定以下structimpl

代码语言:javascript
复制
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() {}

我收到一条关于终身问题的错误消息:

代码语言:javascript
复制
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的迭代器?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-11-05 10:38:47

您不能这样做,因为它将允许您绕过运行时检查唯一性冲突。

RefCell为您提供了一种将可更改性排他性检查“推迟”到运行时的方法,以交换允许通过共享引用对其内部数据进行突变。这是使用RAII警卫完成的:您可以使用对RefCell的共享引用获得一个保护对象,然后使用这个保护对象访问RefCell中的数据:

代码语言:javascript
复制
&'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引用而不需要对RefCell&mut引用。然而,这些引用将被链接到守卫,并且不能比守卫活得更久。这是有意做到的:RefRefMut析构函数在其RefCell中切换各种标志,以强制进行可更改性检查,并在检查失败时强制borrow()borrow_mut()恐慌。

您可以做的最简单的事情是返回Ref的包装器,该引用将实现IntoIterator

代码语言:javascript
复制
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()
    }
}

(试试吧,在操场上)

您不能直接为VecRefWrapper实现VecRefWrapper,因为内部Ref将由into_iter()使用,这给您提供了与现在的情况基本相同的情况。

票数 17
EN

Stack Overflow用户

发布于 2021-08-11 07:15:53

交替解

这里有一个替代的解决方案,它使用内部可修改性,正如它所期望的那样。我们应该为&T值创建一个迭代器,而不是为Ref<T>值创建一个迭代器,这个迭代器会自动执行。

代码语言:javascript
复制
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,
        }
    }
}

游乐场

解释

公认的答案有几个明显的缺点,可能会混淆那些新的锈菌。我将解释,在我个人的经验,被接受的答案可能实际上是有害的初学者,以及为什么我相信这种替代使用内部可变和迭代器,他们的意图。

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

代码语言:javascript
复制
  RefCell<T>       .borrow()  ->     Ref<T>      .deref()  ->       &T
  RefCell<T>   .borrow_mut()  ->  RefMut<T>  .deref_mut()  ->   &mut T

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

代码语言:javascript
复制
Ref::map(Ref<T>, ...)  ->  Ref<U>

Ref::map和其对应的RefMut::map是内部变化模式的真正明星,not borrow()borrow_mut()

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

考虑将first()方法添加到问题中描述的Foo结构中:

代码语言:javascript
复制
fn first(&self) -> &u32 {
      &self.bar.borrow()[0]
}

不,.borrow()做了一个临时的Ref,它只存在于方法返回之前:

代码语言:javascript
复制
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

如果我们将其分解并使隐含的尊重显式化,那么我们可以更加清楚地了解正在发生的事情:

代码语言:javascript
复制
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将帮助我们对组件(内部)值执行以下操作:

代码语言:javascript
复制
fn first(&self) -> Ref<u32> {
    Ref::map(self.bar.borrow(), |bar| &bar[0])
}

当然,.deref()仍然会自动发生,而Ref<u32>通常都是以&u32的形式透明的。

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

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

附加思想

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

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

代码语言:javascript
复制
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实现的,而不是针对拥有的类型VecRefWrapper实现的。

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

代码语言:javascript
复制
for &i in &foo.iter() {
    println!("{}", i);
}

这是一个微妙和令人困惑的区别,如果你是生疏。为什么我们必须引用该值时,它是匿名拥有--而且应该只存在于--循环的范围?

最后,上面的解决方案展示了如何通过内部可更改的方式钻取所有它们到数据中,并使实现可变迭代器的路径变得清晰。使用RefMut

票数 3
EN

Stack Overflow用户

发布于 2021-12-26 15:02:15

从我的研究来看,目前还没有解决这个问题的办法。这里最大的问题是自我引用和铁锈不能证明你的代码是安全的。或者至少不是以一般的方式。

我认为,假设像奥罗伯罗斯自胞参考这样的板条箱是解决方案,如果您知道您的结构(T in Ref<T>)不包含任何智能指针,也不包含任何可能使您在“依赖”结构中获得的指针无效的指针,这是安全的。

请注意,self-cell通过额外的堆分配来安全地完成这一任务,在某些情况下可能是可以的。

也有map_valueRef<T>,但正如您所看到的,通常总是有一些方法使指针失效(这并不意味着您的具体情况是错误的-它可能永远不会添加到核心库/语言中,因为它不能保证任何T)。

是啊,所以没人接抱歉。impl IntoIterator for &T可以工作,但我认为这是相当的黑客,它迫使您编写for x in &iter而不是for x in iter

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33541492

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档