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 instd
providingiter()
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()
}
}