简体   繁体   English

如何在 Rust 中的通用 function 中同时使用非拥有迭代器和消耗迭代器?

[英]How to use both non-owning iterator and consuming iterator in a generic function in Rust?

In Rust, we can use .iter() on various collection to create a non-owning iterator which returns references on a collection like Vec.在 Rust 中,我们可以在各种集合上使用.iter()来创建一个非拥有迭代器,它返回对像 Vec 这样的集合的引用。 We can also use .into_iter() to create a consuming iterator which then returns values moved out of the collection.我们还可以使用.into_iter()创建一个消费迭代器,然后返回从集合中移出的值。 There is no trait for .iter() like there is for .into_iter() , but we can achieve the same thing by calling .into_iter() on a reference to the collection. .iter() .into_iter()的特征,但是我们可以通过在集合的引用上调用.into_iter()来实现同样的目的。

For example, this function compiles fine:例如,这个 function 编译得很好:

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
}

I want to make this function generic.我想让这个 function 通用。 I want it to accept not just a Vec of i32, but also any other collection of i32 that happens to implement IntoIterator<Item=i32>.我希望它不仅接受 i32 的 Vec,而且接受恰好实现 IntoIterator<Item=i32> 的任何其他 i32 集合。

Doing that seems simple enough, yet the following generic function no longer compiles.这样做看起来很简单,但下面的通用 function 不再编译。

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
}

Compilation fails with the following error:编译失败并出现以下错误:

    |     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

I don't quite understand this part of the error:我不太明白这部分错误:

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

I'm not trying to copy a value of type T. I'm trying to copy a value of type &T , ie a reference to T, not T itself.我不是要复制 T 类型的值。我是要复制&T类型的值,即对 T 的引用,而不是 T 本身。 I thought you could copy non-mutable references without issues.我认为您可以毫无问题地复制非可变引用。 Why would it be required for T, not &T, to implement Copy?为什么需要 T 而不是 &T 来实现 Copy?

In the context of a generic function, the only things exist for the type are from the bounds.在通用 function 的上下文中,该类型唯一存在的东西来自边界。 If you specify T: IntoIterator<Item = i32> , then only T implements IntoIterator , &T does not.如果指定T: IntoIterator<Item = i32> ,则只有T实现IntoIterator&T不实现。 Of course, autoderef kicks in, dereferencing the reference but trying to move the value out of it.当然,autoderef 启动了,取消对引用的引用,但试图将值移出它。

If you want to specify that &T implements IntoIterator , the way to do that is as follows:如果要指定&T实现IntoIterator ,方法如下:

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

The constraint you have written is for vec to be IntoIterator .您编写的约束是vecIntoIterator This includes collections (like a Vec ) which have the .iter() method to get non-consuming iterators, but also any other iterators.这包括 collections(如Vec ),它具有.iter()方法来获取非消耗迭代器,但也包括任何其他迭代器。 If vec is a stream where data are consumed and then immediately thrown into the void, your signature is perfectly matched (this is an iterator over i32 ), but taking a reference to your stream does not guarantee that you can iterate over references on i32 : regardless of what you do with the integers received, you called the stream and lost the 'previous' value.如果vec是一个 stream ,其中数据被消耗然后立即被扔进空白,你的签名是完美匹配的(这是i32上的迭代器),但是引用你的 stream不能保证你可以迭代i32上的引用:无论您如何处理收到的整数,您都会调用 stream 并丢失“先前”值。

There are several solutions there:那里有几种解决方案:

  • If you absolutely want to keep the signature the same, you can build a cache.如果你绝对想保持签名不变,你可以建立一个缓存。 You can be not really smart about it (collect the whole iterator into a Vec or something similar, and then do whatever you want with a known data structure, or do something finer where the iterator is called, and a cache is built incrementally, depending on your needs).你可能不是很聪明(将整个迭代器收集到Vec或类似的东西中,然后用已知的数据结构做任何你想做的事情,或者在迭代器被调用的地方做一些更好的事情,并且缓存是增量构建的,这取决于根据您的需要)。
  • Conversely, if you know that all your inputs are going to be collections (ie a bunch of elements, all available with random access, but don't care if it's a Vec , a slice, an array or something else), you need to express that in your signature:相反,如果您知道所有输入都将是collections (即一堆元素,所有元素都可以随机访问,但不关心它是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]);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM