I have code that looks like this (I've simplified it to remove extraneous complications):
fn do_step(gs: GuardState) -> Option<GuardState>{}
fn multi_step(init_state: GuardState) -> GuardState{
let mut gs = init_state;
while let Some(new_gs) = do_step(gs){
gs = new_gs;
}
gs
}
What it's supposed to do is keep applying do_step to its output until we hit a None, and then return that output.
This doesn't work, because I can't return the moved value gs.
I think it would work if I refactored it so that do_step takes a reference, but I would prefer not to have to do that, given the knock-changes to other stuff that might involve... I could do while true and then use match to return in the None branch, but I don't really like using while true and it feels like there should be a more elegant way to handle this... Is there a better way to implement "keep applying do_step until you get None" that I am missing?
In Rust, you'd normally use a loop expression instead of while true, but neither solves your problem. The moment you movegs to do_step, you can't return it from your function; you don't own it any more—it is now owned by do_step. Immediate solutions I could think of without knowing your real use-case would be (I) inlining do_step so that ownership doesn't change, (II) passing gs via (mutable) reference, or (III) you could pass a clone or copy of gs to do_step.
Thanks. Yeah I thought about it some more and I think I understand now why I can't do what I want. Good to have the possible ways forward laid out. I didn't think of inlining. (I don't think it'll work for me, but it's good to have it in mind for next time maybe).
I guess the easiest thing to do would be to have do_step() indicate which is the final value using its return value, instead of returning None and forcing the caller to keep track of it.
For example, you could define an enum with two states, one that indicates the function needs to be applied again, and another that indicates that computation has finished. You then loop and match until you get the finished state.
I feel like this would make a good standard library ControlFlow combinator.
impl<V> ControlFlow<V, V> {
/// Repeatedly applies the function to the initial value until
/// ControlFlow::Break is returned.
pub fn apply_loop<F>(initial: V, f: F) -> V
where F: FnMut(V) -> ControlFlow<V, V> { ... }
}
Well, it's basically try_fold on an infinite iterator with an unwrapped return value.
pub fn apply_loop<F, V, R>(initial: V, mut f: F) -> R // a bit more generalized.
where F: FnMut(V) -> ControlFlow<R, V> // the final result type doesn't
{ // really need to be the same as V.
iter::repeat(())
.try_fold(initial, |acc, ()| f(acc))
.break_value()
.unwrap()
}
Could still be interesting to add to the standard library if there are enough use cases that fit into the "loop on a state variable until break" pattern.