Rust: trait method returning consuming and non consuming IntoIter


#1

If my understanding is correct, when I have a struct MyStruct, I can do both of these below:

impl IntoIter for MyStruct
impl <'a> IntoIter for &'a MyStruct

to get me both a consuming and a non consuming iterators so that I can do:

let dataset :MyStruct = out_of_thin_air();
//borrow dataset immutably
for item in &dataset {
    //do something
}
//mutable borrow
for item in dataset {
    //do something else
}

I want the same behaviour from an object returned by a method which will be part of a Trait, like:

pub trait DataSource {
    get_data(&self) -> T where T: Please_Help
}

Once I impl DataSource for MyStruct and MyAnotherStruct, I can do:

let another_dataset: [MyAnotherStruct or MyStruct] = somehow();
// non consuming iteration
for item in &(another_dataset.get_data()) {
    //do something
}
// consuming iteration
for item in another_dataset.get_data() {
    //do something else
}

Is it possible to have one get_data() in a trait to return an object that can impl IntoIter with and without consuming the object ?


#2

You can’t have .get_data() that magically changes return type depending on whether & is applied to its result later.

You’d probably want:

for item in &another_dataset {}
for item in another_dataset {}

This works, because it’s equivalent to:

for item in (&another_dataset).into_iter() {}
for item in (another_dataset).into_iter() {}

And support convention of having another_dataset.iter() and another_dataset.into_iter().


#3

As @kornel mentioned, you can follow the same pattern as Vec does, where depending on whether you have an owned value or borrowed, you get a certain type of IntoIterator impl. So something like:

trait DataSource {
    type Iter: IntoIterator; // maybe use another associated type for the item type yielded by the iterator
    
    fn get_data(self) -> Self::Iter;  // note that you need to take `self`, not `&self`
}

struct MyStruct(Vec<i32>);

impl DataSource for MyStruct {
    type Iter = std::vec::IntoIter<i32>;
    
    fn get_data(self) -> Self::Iter {
        self.0.into_iter()
    }
}

impl<'a> DataSource for &'a MyStruct {
    type Iter = std::slice::Iter<'a, i32>;
    
    fn get_data(self) -> Self::Iter {
        self.0.iter()
    }
}

So now once you have an instance of MyStruct (or MyAnotherStruct), you can decide whether to use their owned or borrowed IntoIterator versions.


#4

@vitalyd Thank you very much for the detailed answer. I just have one more concern:

In DataSource trait, we have the bound IntoIterator for type Iter. But in impl <'a> DataSource for &'a MyStruct, the Iter type is std::slice::Iter<'a, i32>. Looking at https://doc.rust-lang.org/stable/std/slice/struct.Iter.html, this struct does not implement IntoIterator, but implements Iterator. Isnt this violating the trait bound ?


#5

Every Iterator is itself an IntoIterator thanks to this blanket impl.


#6

Interesting! Thank you so much!