在 Rust 中创建一个迭代器,生成对切片元素的可变引用对,以及剩余元素上的迭代器

问题描述 投票:0回答:2

我可以通过使用自由函数和 fn 指针来实现所需的行为,而无需自定义迭代器:

use std::slice::Iter;
use std::iter::Chain;

///Apply the function f to each element of the slice and the iterator of the remaining elements
pub fn cross<T>(slice: &mut [T], f: fn (&mut T,  Chain<Iter<'_,T>, Iter<'_,T>>)) {
    for i in 0..slice.len() {
        let (preceding, subsequent) = slice.split_at_mut(i);
        let (current_entity, subsequent) = subsequent.split_first_mut().unwrap();
        let iter = preceding.iter().chain(subsequent.iter());
        f(current_entity, iter);
    }
}

可以像这样使用:

let mut testvec = vec![1,2,3,4,5];

cross(&mut testvec, |elem, rest| {
    println!("{} {:?}", elem, rest.collect::<Vec<_>>());
});

哪个打印:

1 [2, 3, 4, 5]
2 [1, 3, 4, 5]
3 [1, 2, 4, 5]
4 [1, 2, 3, 5]
5 [1, 2, 3, 4]

但是,当我尝试将其制作为自定义迭代器结构时,我收到了终身错误:

use std::slice::Iter;
use std::iter::Chain;

/// yield pairs of mutable references to elements of the slice, along with an iterator over the remaining elements
pub struct CrossIter<'a, T> {
    slice: &'a mut [T],
    index: usize,
}

impl<'a, T> CrossIter<'a, T> {
    pub fn new(slice: &'a mut [T]) -> Self {
        CrossIter {
            slice,
            index: 0,
        }
    }
}

/// yield pairs of mutable references to elements of the slice, along with an iterator over the remaining elements
impl<'a, T> Iterator for CrossIter<'a, T> {
    type Item = (&'a mut T, Chain<Iter<'a, T>, Iter<'a, T>>);

    fn next(&mut self) -> Option<Self::Item> {
        if self.index < self.slice.len() {
            let (preceding, subsequent) = self.slice.split_at_mut(self.index);
            let (current_entity, subsequent) = subsequent.split_first_mut().unwrap();
            let iter = preceding.iter().chain(subsequent.iter());
            self.index += 1;
            Some((current_entity, iter))
        } else {
            None
        }
    }
}

fn main() {
    let mut textvec = vec![1, 2, 3, 4, 5];
    let iter = CrossIter::new(&mut textvec);

    for (elem, rest) in iter {
        println!("{} {:?}", elem, rest.collect::<Vec<_>>());
    }
}

错误是:

error: lifetime may not live long enough
  --> src\iterator.rs:27:13
   |
18 | impl<'a, T> Iterator for CrossIter<'a, T>  where Self: 'a {
   |      -- lifetime `'a` defined here
...
21 |     fn next(&mut self) -> Option<Self::Item> {
   |             - let's call the lifetime of this reference `'1`
...
27 |             Some((current_entity, iter))
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
   |
   = note: requirement occurs because of a mutable reference to `T`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

我想我理解了这个错误。迭代器的

next
签名期望生成的值具有通用生命周期
'a
'a
是 CrossIter 内切片的生命周期。然而,它会产生与 self 具有相同生命周期的东西(即
'1
),其中 self 是对 CrossIter 结构实例的引用。

我想我需要告诉它,CrossIter 借用的切片的寿命至少与 CrossIter 本身一样长。

我该如何解决这个问题?

rust iterator borrow-checker
2个回答
1
投票

不幸的是,这是无法做到的。

原因是,根据定义,迭代器允许您将不同时间生成的元素保留在一起。这是必要的,例如

collect()
可以工作,因为它将所有元素放在一个集合中。

但是这个属性从根本上来说与你的迭代器不兼容:例如,在第一次迭代中,你生成一个包含元素二的迭代器,而在下一次迭代中,你生成一个对其的可变引用。如果您保持两者都处于活动状态,则可以从迭代器获取对元素二的引用,并且对它的可变和不可变引用共存,这是 Rust 不允许的。

它与您的函数配合使用的原因是因为它不允许您同时保持两个元素处于活动状态:它只允许您逐一迭代它们。

有人希望所谓的“借出迭代器”或“流式迭代器”,它们将具有与您的函数相同的属性:不允许在不同迭代中生成的项共存。有了 GAT(通用关联类型),就可以编写这样的特征(LendingIterator),但它尚未在标准库中定义,并且没有语法糖(如 for 循环)。

    
根据 Chayim 的回答,我编写了一个贷款迭代器来实现这一目标。


0
投票

可以像这样使用:

fn main() {
    let mut testvec = vec![1, 2, 3, 4, 5];
    let mut iter = testvec.cross();
    while let Some((elem, rest)) = iter.next() {
        println!("{} {:?}", elem, rest.collect::<Vec<_>>());
    }
}

哪个输出:

1 [2, 3, 4, 5]
2 [1, 3, 4, 5]
3 [1, 2, 4, 5]
4 [1, 2, 3, 5]
5 [1, 2, 3, 4]

© www.soinside.com 2019 - 2024. All rights reserved.