如何实现一个给生命周期结构的迭代器?

时间:2016-05-21 17:42:36

标签: iterator rust lifetime

我想创建一个控制修改的可变迭代器,为此我创建了一个名为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

1 个答案:

答案 0 :(得分:2)

除非你没有实现标准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参数的生命周期相关联。在初始化结果wheel的{​​{1}}字段时,编译器将自动从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,这就是您遇到编译错误的原因。< / p>

如果Rust不允许重新生成,那么您不必在'b中存储&'b mut RouletteWheel<T>,而是必须存储FitnessModifier,其中&'b &'a mut RouletteWheel<T>是生命周期'aRouletteWheel<T>'b&'a mut RouletteWheel<T>的生命周期。但是,Rust允许我们将此引用“折叠”为引用,我们只能存储FitnessIterMut<'a, T>(生命周期为&'b mut RouletteWheel<T>,而不是'b,因为'a是寿命缩短。

此更改的最终效果是,在您致电'b后,在结果next超出范围之前,您根本无法使用FitnessIterMut。这是因为Option<FitnessModifier<'b, T>>是从FitnessModifier借用的,并且由于该方法通过可变引用传递self,编译器假定self保持对{{1}的可变引用或者它的一个字段(这里是真的,一般情况下并非总是如此)。因此,虽然范围内有FitnessModifier,但FitnessIterMut只有一个可用的可变别名,这是FitnessModifier对象中的一个。当RouletteWheel超出范围时,FitnessModifier对象将再次可用。

如果您完全需要符合FitnessModifier<'b, T>特征,那么我建议您使用FitnessIterMut替换您的可变引用。 Rc未实现Iterator,但它实现Rc<RefCell<T>>(仅克隆指针,而不是基础数据),因此您需要显式调用Copy来克隆CloneRefCell在运行时进行动态借用检查,这会带来一些运行时开销,但可以让您更自由地传递可变对象。