我想创建一个控制修改的可变迭代器,为此,我创建了一个名为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。此时,FitnessIterMutFitnessModifier都包含对RouletteWheel对象的可变引用,并且FitnessIterMutFitnessModifier仍然可用–这是不合法的!我可以再次在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>>,其中'bself参数的生存期链接。当初始化结果wheelFitnessModifier字段时,编译器将自动从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>,其中'aRouletteWheel<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/

10-09 17:16