Nob*_*bbZ 8 iterator lifetime rust
鉴于以下struct
和impl
:
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)
我怎么能返回并使用bar
s迭代器?
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
.但是,这些参考文献将与警卫相关联,并且不能比守卫更长寿.这是有意这样做:Ref
和RefMut
析构函数切换其内部的各种标志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()
,给你基本上与你现在相同的情况.
这是一个替代解决方案,它按预期使用内部可变性。&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::map
Ref<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::mut
andRefMut::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 方法消耗self
:fn 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
。