Rfcs: Pre-RFC: Save and load state for `Iterator`

Created on 7 Oct 2018  ·  7Comments  ·  Source: rust-lang/rfcs

On my project, I recently found a problem that I can't implement a comfortable interface that do operation on Iterator directly using existing API.
rust /* not work because iterator state is changed after each operation */ pub fn do_operateion<'a, T, U>(input_iter: &mut T, output_iter: &mut U) where T: Iterator<Item = &'a f32>, U: Iterator<Item = &'a mut f32>, { let temp = input_iter.skip(2).map(...).sum(); output_iter.step_by(2).zip(input_iter).for_each(|a, b| *a * temp); output_iter.skip(1).step_by(2).zip(input_iter).for_each(|a, b| *a * temp * 123.0); }

Hence the idea of adding StateSavableIterator trait:
````rust
pub trait StateSavableIterator: Iterator {
type IteratorState;

fn save(&self) -> IteratorState;
fn load(&mut self, state: &IteratorState) -> Self;

}
````

So that I can:
rust pub fn do_operateion<'a, T, U>(input_iter: &mut T, output_iter: &mut U) where T: StateSavableIterator<Item = &'a f32>, U: StateSavableIterator<Item = &'a mut f32>, { let input_init = input_iter.save(); let out_init = output_iter.save(); let temp = input_iter.load(input_init).skip(2).map(...).sum(); output_iter.load(out_init).step_by(2).zip(input_iter.load(input_init)).for_each(|a, b| *a * temp); output_iter.load(out_init).skip(1).step_by(2).zip(input_iter.load(input_init)).for_each(|a, b| *a * temp * 123.0); }

We don't have to pass &slice anymore! This trait benefits to writing more generic and readable API.

T-libs

Most helpful comment

slice::IterMut<T> could not implement StateSavableIterator for exactly the same reason. Save, call next() to get a &mut T, load, call next again and get a second &mut T aliasing the same item.

All 7 comments

You can use the Clone trait for this. Require a T: Iterator<Item = …> + Clone bound, then iter.save() is iter.clone() and iter.load(x) is iter = x.

You can use the Clone trait for this. Require a T: Iterator<Item = …> + Clone bound, then iter.save() is iter.clone() and iter.load(x) is iter = x.

Clone does not work on IterMut because of noalias rule.
But an output_iter have to be an iterator that has &mut Item

slice::IterMut<T> could not implement StateSavableIterator for exactly the same reason. Save, call next() to get a &mut T, load, call next again and get a second &mut T aliasing the same item.

To give a more helpful answer, if output_iter in your code is indeed slice::IterMut, consider passing the actual slice instead and calling .iter_mut() on it twice. This will let the borrow checker make sure that the lifetimes of the two iterators do not overlap. (Until NLL lands you’ll also likely need to add a {…} anonymous block to limit the scope of the first iterator.)

Iterator::by_ref almost sounds like what you want here.

If you actually want to abstract slice::IterMut to restart iteration, then an for<'a> &'a mut T: IntoIterator bound should theoretically work, except right now a compiler bug makes this mostly useless.

In my opinion, the best solution would be FnBorrow* closure traits that supports reborrowing in closures by providing access to the lifetime of the closure's self reference. It'd make tricks like this work.

fn foo<F>(f: F) where F: FnBorrowMut() -> impl Iterator<Item: 'borrow> { ... }

foo(|| my_collection.iter_mut().take(5).filter(bar))

In the short term, foo<B: BorrowMut<T>>(&mut [B]) is more flexible than foo(&mut [T]).

I don't see any case where Clone isn't viable but a load-and-save state is both viable and sound.

Closing as per the above comments, this sort of interface does not seem to solve the problem.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3442853561 picture 3442853561  ·  3Comments

camden-smallwood-zz picture camden-smallwood-zz  ·  3Comments

torkleyy picture torkleyy  ·  3Comments

onelson picture onelson  ·  3Comments

marinintim picture marinintim  ·  3Comments