替代解决方案
这是一个替代解决方案,它按预期使用内部可变性。我们应该为Ref<T> 值创建一个迭代器,而不是为&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::mut 和RefMut::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> 归方法的范围所有,并且没有返回,因此甚至在它提供的引用可以使用之前就被删除了。所以,我们真正需要的是返回一个拥有的类型而不是一个引用。我们想返回一个Ref<T>,因为它为我们实现了Deref!
Ref::map 将帮助我们为组件(内部)值做到这一点:
fn first(&self) -> Ref<u32> {
Ref::map(self.bar.borrow(), |bar| &bar[0])
}
当然,.deref() 仍然会自动发生,而Ref<u32> 将主要是引用透明的 &u32。
知道了。 使用Ref::map 时容易犯的一个错误是尝试在闭包中创建一个拥有的值,这在我们尝试使用borrow() 时是不可能的。考虑第二个参数的类型签名,函数:FnOnce(&T) -> &U,。它返回一个引用,而不是一个拥有的类型!
这就是为什么我们在答案&v[..] 中使用切片而不是尝试使用向量的.iter() 方法,该方法返回一个拥有的std::slice::Iter<'a, T>。切片是一种引用类型。
其他想法
好的,现在我将尝试证明为什么这个解决方案比公认的答案更好。
首先,IntoIterator 的使用与 Rust 标准库不一致,并且可以说是 trait 的目的和意图。 trait 方法消耗 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。
接下来,为引用 &VecRefWrapper 而不是拥有的类型 VecRefWrapper 实现 IntoIterator 特征。
假设您正在实现一个库。您的 API 的使用者将不得不使用引用运算符看似随意地装饰拥有的值,如操场上的示例所示:
for &i in &foo.iter() {
println!("{}", i);
}
如果您是 Rust 新手,这是一个微妙而令人困惑的区别。为什么我们必须引用该值,因为它是匿名拥有的 - 并且应该只存在于 - 循环的范围内?
最后,上面的解决方案展示了如何通过内部可变性深入到您的数据中,并使实现mutable iterator 的路径也变得清晰。使用RefMut。