Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Whats the best way to write an iterator supporting multiple logic branches?

Tags:

iterator

rust

In Rust I've started writing iterators, converting them from code which took a callback function.

I ran into the problem where the code that used a callback in multiple branches of the function didn't convert so cleanly into a Rust iterator.

To give some pseudo-code.

// function using callbacks where the caller can exit at any time,
// can be used in a similar way to an iterator.
fn do_stuff(args, callback_fn(cb_args)) {
    // define a, b, c... args
    if callback_fn(a, b, 0) == false { return; }
    for i in 0..n {
        if callback_fn(c, d, i) == false { return; }
    }
    if callback_fn(e, f, -1) == false { return; }
}

Converting this to an iterator was rather awkward since I needed to store some state representing each branch.

impl Iterator for MyStruct {
    fn next(&mut self) -> Option<MyResult> {
        let out = match (self.state) {
            0 => {
                self.state += 1;
                Some(MyResult(self.a, self.b, 0))
            },
            1 => {
                self.i += 1;
                if self.i == self.n {
                    self.state += 1;
                }
                Some(MyResult(self.c, self.d, self.i - 1))
            },
            2 => {
                self.state += 1;
                Some(MyResult(self.e, self.f, -1))
            },
            _ => {
                None
            },
        }
        return out;
    }
// --- snip

With the example above, this is arguably acceptable, (if a little awkward). Consider cases with multiple for loops, variable scopes, where its much harder to track state.


While I didn't try these, I imagine there are some ways to achieve this which in most cases are less-then-ideal workarounds:

  • Using the callback version, building a vector, then iterating over it...
    (works but defeats the purpose of using an iterator, no way to early exit and avoid creating the entire data set for eg).
  • Writing an iterator which communicates with a thread that uses similar logic to the callback version.
    (while possible, the overhead of creating OS threads makes it a poor choice in many cases).

Besides the workarounds above:

Are there ways to write iterators like the example given, with less convoluted logic?
Ideally more like the example that uses callbacks.
Otherwise are there other ways to handle this?

Or is this simply not supported in Rust?


Note, the same logic applies coming from Python generators (using yield instead of a callback, using callbacks as an example here since they're ubiquitous with first class functions).

like image 578
ideasman42 Avatar asked Oct 21 '25 06:10

ideasman42


1 Answers

Languages like C# and Python provide a way to generate iterators from methods written using a special yield keyword. As of Rust 1.11, there is no such feature in the language. However, such a feature is planned (see RFC) (indeed, yield is a reserved keyword!) and would likely work as in C# (i.e. the compiler would generate a struct with the necessary state and implementation for Iterator).

In the meantime, you could try Stateful, a project that attempts to provide this feature. (This blog post explains how Stateful works, and the challenges involved.)

like image 109
Francis Gagné Avatar answered Oct 23 '25 23:10

Francis Gagné