I am quite new to Rust and want to clear some basic doubt about Iterators
.
Iterator
is implemented for a simple Counter
struct. I assume this also causes IntoIterator
to also be implemented for Counter
. However I want to implement IntoIterator
for &Counter
.
What should IntoIterator
return in this case? A new Iterator type or Counter
?
Edit: Link fixed.
One way you can make this work is by wrapping the inner value in a Cell
, but this allows behavior that may be surprising to most Rust programmers: In most cases, you can rely on a for
loop to not skip any elements because it has exclusive access to the iterator. But this doesn't have that property: Because it uses interior mutability, it's possible to advance the iterator while the loop is running.
use std::cell::Cell;
struct Counter(Cell<usize>);
impl Counter {
pub fn get_last_value(&self) -> usize {
self.0.get()
}
pub fn new(init:usize)->Self {
Counter(Cell::new(init))
}
}
impl Iterator for &Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
let v = self.0.get();
if v < 20 {
self.0.set(v+1);
Some(v)
} else {
None
}
}
}
fn main()
{
let c = Counter::new(10);
// Looping this way works as 'c' is borrowed by Iterator::next()
//while let Some(i) = c.next() {
// println!("{i}");
// println!("Remaining: {}", c.get_last_value());
//}
for i in &c {
println!("{i}");
println!("Remaining: {}", c.get_last_value());
}
}
2 Likes
Thanks. Can you explain if its possible to implement Iterator
over shared reference
of a type without interior mutability.
Only in the most trivial cases (a never-ending iterator that always yields the same thing, for example). To be useful, an Iterator
implementation has to change some kind of internal state when you call next()
. Without internal mutability, there's no place for that state to live inside &Something
.
There may be some confusion caused by the fact that you're calling a method on the iterator, get_last_value
, in addition to iterating the values.
When using a for
loop, you don't have access to the Iterator itself (Counter
), so you can't call Counter::get_last_value
. This is because the iterator itself is moved or "consumed" by the into_iter
method. Note that this method takes the self
parameter by value, not by reference, and this is what causes it to be moved:
fn into_iter(self) -> Self::IntoIter
So you have to use a while
loop instead (as you did), if you need to call methods on the iterator itself.
It may help to look at one of the std
library iterators that also has methods on the iterator. The iterator for slices, Iter has an as_slice method that returns the remaining portion of the original slice. This method can be called from a while
loop, but not a for
loop.
fn main() {
let s = &[1, 2, 3];
let mut s_iter = s.iter();
while let Some(i) = s_iter.next() {
println!("{}, remaining: {:?}", i, s_iter.as_slice());
}
for i in s {
println!("{}", i);
// there is no way to call as_slice()
}
}
3 Likes
Thanks. This is exactly what I was confused about. So apart from using internal mutability wrappers (which works but might not be the usual way), is having a separate wrapper type (say CounterInterator
) for iterating over Counter
the proper/idiomatic way?
Yes, assuming that Counter is the data you want to iterate and potentially keep around after iteration. In other words, Counter would be in the role of a slice in the std library example I posted.
1 Like
For the OP I'd probably just do this (without knowing more about the motivation anyway).
let mut c = Counter(10);
let iter = &mut c;
while let Some(i) = iter.next() {
println!("{i}");
println!("Remaining: {}", iter.get_last_value());
}
(And if that isn't suitable, I agree the next step is probably a separate CounterIterator
type.)
That's what they did do in their playground, in the commented out code, but then they tried a for
loop unsuccessfully (of course):
// Looping this way works as 'c' is borrowed by Iterator::next()
//while let Some(i) = c.next() {
// println!("{i}");
// println!("Remaining: {}", c.get_last_value());
//}
// Looping this way fails since 'c' is moved by IntoIter::into_iter()
for i in c {
println!("{i}");
println!("Remaining: {}", c.get_last_value());
}
1 Like
Ah, I missed the comment / missed the point I suppose.
1 Like