JelteF/derive_more

Derive `Iterator` trait for tuple struct containing vector

fritzrehde opened this issue · 8 comments

I can't find any documentation on deriving the Iterator trait, only the IntoIterator trait for a tuple struct. I thought something like:

#[derive(Iterator)]
pub struct Operations(Vec<Operation>);

would allow me to do something like

let operations = Operations::default();
for op in operations.iter() {
  ...
}

Is this supported? If not, could this feature be added?

Ah, I think I can just #[derive(Deref)] on the tuple struct and it works. I am fairly new to Rust, and don't really understand why this works. Could someone provide an explanation? I think this could also be added to the documentation in some way given my confusion. Thanks!

Iterator is not one of the traits that derive_more currently has derive logic for. No real reason for not having it though, except time constraints. Feel free to contribute a PR. I'd expect the IntoIterator derive to be a good example.

@fritzrehde the inner type of pub struct Operations(Vec<Operation>); (Vec itself) is not an Iterator too, so it's not something derivable for Operations at all, imho. What is the problem with deriving IntoIterator and use it like:

for op in operations {
  ...
}
// or
for op in &operations {
  ...
}
// or
for op in &mut operations {
  ...
}

?

Maybe this is demonstrates that I am new to Rust: my understanding was that .into_iter() was used to convert (i.e. consume/move) the values of the underlying data structure into an iterator, while .iter() just provides references over the elements. I only want an iterator that only reads from the the Operations structure, so I thought I needed to impl the Iterator trait. But it seems like just adding #[derive(Deref)] to Operations allowed me to call .iter() on it (though I don't quite understand what Deref does). So you're saying I could also impl IntoIterator and then just call for op in &operations to be able to still use operations afterwards?

@fritzrehde yes. You should really here separate collection and iterator concepts. They are not the same. Vec is a collection, containing elements, but is not an Iterator itself. By calling its .iter() method you return a slice::Iter<'_> value, which is an Iterator and iterates over elements contained in the Vec.

When we write #[derive(Trait)] on some type Foo, we usually mean to automatically generate impl Trait for Foo. So, when we write #[derive(Iterator)], this means we want to impl Iterator for Foo, but that is not what we want here, because Vec is not an Iterator and Iterator trait doesn't have iter() method, but has next() method, meaning that it's something that can iterate over some items by itself, changing its state, while by calling .iter() we want to return an iterator over contained elements, not iterate over them by itself.

Next, iter() method doesn't belong to a trait, but is defined directly on slice type. So there is no actual trait in std providing iter() method.

However, there is IntoIterator trait, which has the exact semantics you need. It consumes the collection, returning an Iterator over its elements. If we want to have Iterator<Item = &T> instead of Iterator<Item = T>, we may impl IntoIterator for &Foo instead of impl IntoIterator for Foo. This will consume only a reference to the collection and produce an Iterator over references to its elements. You can see such IntoIterator impls for Vec.

for x in y in Rust actually always calls y.into_iter(). That's why you can pass into the in .. part whatever implementing IntoIterator. IntoIterator is automatically implemented for any Iterator, so it doesn't matter whether you pass an actual Iterator into in .. part, or rather something that can be turned IntoIterator automatically.

let operations: Vec<Operation> = whatever();
for op in operations { // calls `operations.into_iter()`
    let _: Operation = op;
}
for op in operations.iter() { // calls `operations.iter().into_iter()`
    let _: &Operation = op;
}
for op in &operations { // calls `(&operations).into_iter()`
    let _: &Operation = op;
}
for op in &mut operations { // calls `(&mut operations).into_iter()`
    let _: &mut Operation = op;
}

So, for your original needs, you should be OK with the following:

#[derive(IntoIterator)]
pub struct Operations(#[into_iterator(ref)] Vec<Operation>);

let operations = Operations::default();
for op in &operations {
  ...
}

Deref allows you to implicitly convert a reference to Operations into a reference to Vec, so exposing the whole Vec interface for Operations. In such situations this is considered as anti-pattern.

@JelteF I'm closing this, as I do think that Iterator trait itself is not something being trivially derivable.

I have one more similar question: I have the following code snippet:

		let rows: Vec<Row> = izip!(self.lines.iter(), self.selected.iter())
...

self.lines is defined as follows:

#[derive(IntoIterator)]
pub struct Lines {
	#[into_iterator(ref)]
	lines: Vec<Line>,
	field_seperator: Option<String>,
	style: Style,
}

I tried using your advice here as well, but this doesn't compile, I get method .iter() could not be found in Lines, which confuses me. For syntactical clarity, I want to use self.lines.iter(), but currently only &self.lines would work. Why is that (and how can I change it)?

@fritzrehde as I've mentioned before:

Next, iter() method doesn't belong to a trait, but is defined directly on slice type. So there is no actual trait in std providing iter() method.

Thus, you cannot derive it.

However, if you strive for syntactical clarity, you may just define this method for your type:

impl Lines {
    fn iter(&self) -> impl Iterator<Item = &Line> {
        (&self.lines).into_iter()
    }
}