Home > Software design >  How to use both non-owning iterator and consuming iterator in a generic function in Rust?
How to use both non-owning iterator and consuming iterator in a generic function in Rust?

Time:12-08

In Rust, we can use .iter() on various collection to create a non-owning iterator which returns references on a collection like Vec. We can also use .into_iter() to create a consuming iterator which then returns values moved out of the collection. 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.

For example, this function compiles fine:

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. 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>.

Doing that seems simple enough, yet the following generic function no longer compiles.

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, i.e. a reference to T, not T itself. I thought you could copy non-mutable references without issues. Why would it be required for T, not &T, to implement Copy?

CodePudding user response:

In the context of a generic function, the only things exist for the type are from the bounds. If you specify T: IntoIterator<Item = i32>, then only T implements IntoIterator, &T does not. Of course, autoderef kicks in, dereferencing the reference but trying to move the value out of it.

If you want to specify that &T implements IntoIterator, the way to do that is as follows:

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

CodePudding user response:

The constraint you have written is for vec to be IntoIterator. This includes collections (like a Vec) which have the .iter() method to get non-consuming iterators, but also any other iterators. 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.

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).
  • Conversely, if you know that all your inputs are going to be collections (i.e. 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:
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]);
}
  • Related