我想创建一个控制修改的可变迭代器,为此,我创建了一个名为FitnessIterMut
的结构,该结构impl
Iterator
特性。next()
方法提供了一个结构,该结构可以在完成修改后对容器本身执行操作。 (这是做这种事情的好方法吗?)
pub struct FitnessModifier<'a, T: 'a> {
wheel: &'a mut RouletteWheel<T>,
value: &'a mut (f32, T)
}
impl<'a, T> FitnessModifier<'a, T> {
pub fn read(&'a self) -> &'a (f32, T) {
self.value
}
pub fn set_fitness(&'a self, new: f32) {
let &mut (ref mut fitness, _) = self.value;
self.wheel.proba_sum -= *fitness;
self.wheel.proba_sum += new;
*fitness = new;
}
}
pub struct FitnessIterMut<'a, T: 'a> {
wheel: &'a mut RouletteWheel<T>,
iterator: &'a mut IterMut<'a, (f32, T)>
}
impl<'a, T> Iterator for FitnessIterMut<'a, T> {
type Item = FitnessModifier<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(value) = self.iterator.next() {
Some(FitnessModifier { wheel: self.wheel, value: value })
}
else {
None
}
}
}
这给了我这个错误,我想我必须做一个
'b
一生,但是我有点迷路了。error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements [E0495]
Some(FitnessModifier { wheel: self.wheel, value: value })
^~~~~~~~~~
help: consider using an explicit lifetime parameter as shown: fn next(&'a mut self) -> Option<Self::Item>
fn next(&mut self) -> Option<Self::Item> {
if let Some(value) = self.iterator.next() {
Some(FitnessModifier { wheel: self.wheel, value: value })
}
else {
None
最佳答案
除非您可以不实现标准的Iterator
特征,否则您将无法使它与简单的可变引用一起使用。这是因为在Rust中,对一个特定值同时具有多个可用的可变别名是不合法的(因为这可能导致内存不安全)。让我们看看为什么您的代码违反了此限制。
首先,我可以实例化FitnessIterMut
对象。从该对象,我可以调用next
以获得FitnessModifier
。此时,FitnessIterMut
和FitnessModifier
都包含对RouletteWheel
对象的可变引用,并且FitnessIterMut
和FitnessModifier
仍然可用–这是不合法的!我可以再次在next
上调用FitnessIterMut
以获得另一个FitnessModifier
,现在我对RouletteWheel
具有3个可变别名。
您的代码无法编译,因为您假设可以复制可变引用,而事实并非如此。不变引用(&'a T
)实现Copy
,但可变引用(&'a mut T
)不实现,因此无法复制。
我们可以做些什么来解决这个问题? Rust使我们可以通过重新借用暂时使可变引用不可用(即,如果尝试使用它,则会出现编译器错误)。通常,对于未实现Copy
的类型,编译器将移动值而不是将其复制,但是对于可变引用,编译器将重新借款而不是移动。重新借用可以看作是对引用的“扁平化”或“崩溃”引用,同时保持最短的生命周期。
让我们看看这在实践中是如何工作的。这是next
的有效实现。请注意,这不符合标准Iterator
特性的约定,因此我将其设为固有方法。
impl<'a, T> FitnessIterMut<'a, T> {
fn next<'b>(&'b mut self) -> Option<FitnessModifier<'b, T>> {
if let Some(value) = self.iterator.next() {
Some(FitnessModifier { wheel: self.wheel, value: value })
}
else {
None
}
}
}
现在,我们不返回
Option<FitnessModifier<'a, T>>
,而是返回Option<FitnessModifier<'b, T>>
,其中'b
与self
参数的生存期链接。当初始化结果wheel
的FitnessModifier
字段时,编译器将自动从self.wheel
中重新借用(我们可以通过编写&mut *self.wheel
而不是self.wheel
使其明确)。由于此表达式引用了
&'a mut RouletteWheel<T>
,因此您认为此表达式的类型也将是&'a mut RouletteWheel<T>
。但是,由于此表达式是从self
借用的&'b mut FitnessIterMut<'a, T>
,因此此表达式的类型实际上是&'b mut RouletteWheel<T>
。在您的代码中,您尝试将&'b mut RouletteWheel<T>
分配给需要&'a mut RouletteWheel<T>
的字段,但是'a
比'b
长,这就是为什么会出现编译器错误的原因。如果Rust不允许重新借用,那么您不必存储
&'b mut RouletteWheel<T>
到FitnessModifier
中,而必须存储&'b &'a mut RouletteWheel<T>
,其中'a
是RouletteWheel<T>
的生存期,'b
是&'a mut RouletteWheel<T>
的生存期在FitnessIterMut<'a, T>
中。但是,Rust使我们可以将该引用“折叠”为引用,并且我们可以只存储&'b mut RouletteWheel<T>
(生存期为'b
,而不是'a
,因为'b
的生存期较短)。此更改的最终结果是,在调用
next
之后,您将无法使用FitnessIterMut
,直到结果Option<FitnessModifier<'b, T>>
超出范围。这是因为FitnessModifier
是从self
借用的,并且由于该方法是通过可变引用传递self
的,因此编译器假定FitnessModifier
保留了对FitnessIterMut
或其字段之一的可变引用(在此是正确的,但并非总是如此一般的)。因此,尽管范围内有一个FitnessModifier
,但RouletteWheel
仅有一个可用的可变别名,它是FitnessModifier
对象中的一个别名。当FitnessModifier<'b, T>
超出范围时,FitnessIterMut
对象将再次变得可用。如果您绝对需要遵循
Iterator
特质,那么我建议您将可变引用替换为Rc<RefCell<T>>
。 Rc
没有实现Copy
,但是实现了Clone
(仅克隆指针,而不克隆基础数据),因此您需要显式调用.clone()
来克隆Rc
。 RefCell
在运行时进行动态借阅检查,这有一点运行时开销,但在如何传递可变对象方面给了您更多自由。关于iterator - 如何实现赋予结构生命周期的迭代器?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/37366203/