如何在Rust的泛型函数中同时使用非拥有迭代器和消费迭代器?

lrpiutwd  于 2022-12-19  发布在  其他
关注(0)|答案(2)|浏览(106)

在Rust中,我们可以在不同的集合上使用.iter()来创建一个非所有者迭代器,它返回一个集合上的引用,比如Vec。我们也可以使用.into_iter()来创建一个消费迭代器,然后返回从集合中移出的值。.iter()没有.into_iter()那样的trait,但是我们可以通过在对集合的引用上调用.into_iter()来实现同样的事情。
例如,此函数编译良好:

fn test_vec(vec: Vec<i32>) {
    let i1 = (&vec).into_iter(); // create a non-owning iterator
    let i2 = (&vec).into_iter(); // create another one

    let i3 = vec.into_iter(); // create an owning iterator which consumes the collection

    // no more non-owning iterators can be created
}

我想让这个函数成为泛型函数,我想让它不仅接受i32的Vec,而且接受i32的任何其他集合,这些集合碰巧实现了IntoIterator〈Item=i32〉。
这样做看起来很简单,但是下面的泛型函数不再编译。

fn test_generic<T: IntoIterator<Item = i32>>(vec: T) {
    let i1 = (&vec).into_iter(); // create a non-owning iterator
    let i2 = (&vec).into_iter(); // create another one

    let i3 = vec.into_iter(); // create an owning iterator which consumes the collection

    // no more non-owning iterators can be created
}

编译失败,出现以下错误:

|     let i1 = (&vec).into_iter(); // create a non-owning iterator
    |              ^^^^^^^-----------
    |              |      |
    |              |      value moved due to this method call
    |              move occurs because value has type `T`, which does not implement the `Copy` trait
    |
note: this function takes ownership of the receiver `self`, which moves value

我不太理解这部分错误:

move occurs because value has type `T`, which does not implement the `Copy`

我不是要复制类型T的值。我是要复制类型**&T**的值,即对T的引用,而不是T本身。我以为你可以毫无问题地复制不可变的引用。为什么需要T而不是&T来实现Copy?

r3i60tvu

r3i60tvu1#

在泛型函数的上下文中,该类型唯一存在的东西来自边界。如果指定T: IntoIterator<Item = i32>,则只有T实现IntoIterator&T不实现。当然,autoderef会启动,解引用引用引用,但试图将值移出引用。
如果要指定&T实现IntoIterator,方法如下:

fn test_generic<T>(vec: T)
where
    T: IntoIterator<Item = i32>,
    for<'a> &'a T: IntoIterator<Item = &'a i32>,
{
nkhmeac6

nkhmeac62#

您编写的约束是vec必须为IntoIterator。(比如Vec),它有.iter()方法来获取非消耗迭代器,但 * 也 * 任何其他迭代器。如果vec是一个流,其中数据被消耗,然后立即被抛出到void中,你的签名是完全匹配的(这是i32上的迭代器),但是接受一个对你的流的引用并不能保证你可以迭代i32上的引用:不管你对接收到的整数做了什么,你调用了流并丢失了“先前”的值。
有几种解决方案:

  • 如果你真的想保持签名不变,你可以建立一个缓存,你可能不太聪明(把整个迭代器收集到一个Vec或类似的东西中,然后对一个已知的数据结构做任何你想做的事情,或者在调用迭代器的地方做一些更好的事情,然后根据你的需要递增地建立一个缓存)。
  • 相反,如果你知道你的所有输入都是 * collection *(也就是一堆元素,都可以随机访问,但不管它是Vec、切片、数组还是其他什么),你需要在签名中表达出来:
fn foo<V>(v: V)
where for<'a> &'a V: IntoIterator<Item=&'a i32>,
      V: IntoIterator<Item=i32>
{
    let itr1 = (&v).into_iter();
    let itr2 = (&v).into_iter();
    for xi in itr1 {
        println!("{}", xi);
    }
    for yi in itr2 {
        println!("{}", yi);
    }
    for zi in v {
        println!("{}", zi);
    }
}
fn main() {
    foo(vec![1,2,3]);
    foo([1,2,3]);
}

相关问题