Function returned from Iterator needs to affect subsequent items of iterator

I want to have an iterator which returns an Item that needs to affect the subsequent items of the iterator.

My current scenario is this

enum Response {
	TaskToDo(Function)
	Text(String)
}

enum Step {
	StepInitial,
	StepX(InitialStepResult),
	StepY,
	StepZ,
}

struct Foo {
	last_step: Step,
}

impl Foo {
	fn new() -> Self {
		Foo { last_step: Step::StepInitial }
	}
}

impl Iterator for Foo {
	type  Item  =  Response;
	
	fn  next(&mut  self)  ->  Option<Self::Item>  {
		match self.last_step {
			StepInitial => handle_initial(self),
			StepX(initial_step_result) => handle_x(self, initial_step_result),
			StepY => handle_y(self),
			StepZ => handle_z(self),
		}
	}
}

fn handle_initial(&mut Foo) -> Response {
	...
	self.last_step = StepX(initial_step_result);
	Response::TaskToDo(|| {
		...
		initial_step_result.somehow_mark_successful();
	})
}

fn handle_x(&mut Foo, result: InitialStepResult) ->(Response {
	...
	if initial_step_result.is_successful() {
		self.last_step = StepY;
		Response::Text("Entered Step Y")
	}
	else {
		self.last_step = StepZ;
		Response::Text("Entered Step Z")
	}
}

fn main() {
    let mut foo = Foo::new();
    for item in foo {
        match item {
            Response::Text(text) => {
                println!("Thing = {}", text);
            }
            Response::Todo(mut func) => {
                func(&mut foo);
            }
        }
    }
}

In this snippet InitialStepResult is a hypothetical type. The Response::TaskToDo(function) needs to somehow communicate back success or failure to Foo using it. Foo will then go to StepY or StepZ based on that.

How do I achieve the behavior of InitialStepResult ? Can it be a Rc<RefCell<Result<_, _>>> or some way to pass a message like channel in futures::channel::oneshot - Rust?

Add some more state to Foo. Make the function that is returned for StepX call into a method of Foo that would mutate the state. Use that state to decide what the next step would be.

That would require the function to hold a mutable reference to foo, yes? Is that possible, for an item from an iterator to hold a mutable reference to the iterator?

I don't think it should be this complicated. You already have a mutable reference to Foo, so you can just use that to modify it.

You can add a field on Foo like so:

struct Foo {
    last_step: Step,
    success: bool // hi :)
}

success starts as false:


impl Foo {
	fn new() -> Self {
		Foo { last_step: Step::StepInitial, success: false}
	}
}

And then once you perform the StepInitial, you can change the result like so:

fn handle_initial(&mut Foo) -> Response {
	...
	self.last_step = StepX(initial_step_result);
	Response::TaskToDo(|| {
		...
		initial_step_result.somehow_mark_successful(); // --
        if successful {        // ++
            foo.success = true // ++
        }                      // ++

	})
}

And then you can use that later on.

2 Likes

The following is an example of what I think you want:

#[derive(Default)]
struct Foo {
    next_step: u32,
    is_successful: bool,
}

enum Response {
    Text(String),
    Todo(Box<dyn FnMut(&mut Foo)>),
}

impl Iterator for Foo {
    type Item = Response;

    fn next(&mut self) -> Option<Self::Item> {
        if self.next_step == 0 {
            Some(Response::Todo(Box::new(|foo: &mut Foo| {
                foo.is_successful = true;
                foo.next_step += 1;
            })))
        } else if self.is_successful && self.next_step < 5 {
            let step = self.next_step;
            self.next_step += 1;
            Some(Response::Text(format!("{}", step)))
        } else {
            None
        }
    }
}

fn main() {
    let mut foo = Foo::default();
    while let Some(item) = foo.next() {
        match item {
            Response::Text(text) => {
                println!("Thing = {}", text);
            }
            Response::Todo(mut func) => {
                func(&mut foo);
            }
        }
    }
}
2 Likes

Ok so it works fine by calling next, but not when I do for item in foo because that implicitly calls into_iter

I'll update my example to add this requirement.

You can change the data types to Rc<Cell<_>> (or Arc<AtomicBool> and Arc<AtomicU32>, or Arc<Mutex<_>>), and when constructing the closure, capture clones of those instead of taking a &mut Foo parameter.

Playground.

1 Like

Thanks, I'll go with this, as in Arc<Mutex<_>>
One last thing, How would I make the closure async?

I believe something like this, but async isn't my area. Someone else may have a better answer.

1 Like