Looking for a design pattern to overcome the error `cannot move out of `handler`, a captured variable in an `FnMut` closure`

I want to define a make_closure function that works as follows:

  • Takes a wrapped closure as input parameter. This closure produces an Result type (i.e., can error).
  • Copies the initial state of wrap in the copy variable.
  • Takes a handler closure as input parameter.
  • Returns a boxed closure that works as follows:
    • When the boxed closure is invoked, it invokes the wrapped closure.
    • If wrapped returns Ok, then it returns Retval::Same.
    • If wrapped returns Error, then it invokes the handler closure, invokes make_closure by passing copy and handler, and returns the result of the function call in form of Retval::Failure.
enum Retval {
    Same,
    Failure(Box<dyn FnMut() -> Retval>),
}

fn make_closure<E>(mut wrapped: impl FnMut() -> Result<(), E> + Clone + 'static, mut handler: impl FnMut(E) + 'static) -> Box<dyn FnMut() -> Retval> {
    Box::new(move || {
        let copy = wrapped.clone();

        return match wrapped() {
            Ok(()) => { Retval::Same }
            Err(error) => {
                handler(error);
                Retval::Failure(make_closure(copy, handler))
            }
        };
    })
}


fn main() {
    let mut counter = 0;
    let mut n_failures = 0;
    let mut cl = make_closure::<u32>(move || {
        counter += 1;
        println!("Closure invoked. Counter: {counter}");
        Ok(()) // Depending on some logic, returns Ok or Err
    }, move |error| {
        n_failures += 1;
        println!("Handler invoked. Failures so far: {n_failures}");
    });
    cl();
}

However, the compiler produces the following error:

error[E0507]: cannot move out of `handler`, a captured variable in an `FnMut` closure
  --> src/main.rs:13:52
   |
6  | fn make_closure<E>(mut cl: impl FnMut() -> Result<(), E> + Clone + 'static, mut handler: impl FnMut(E) + 'static) -> Box<dyn FnMut() -> R...
   |                                                                             ----------- captured outer variable
7  |     Box::new(move || {
   |              ------- captured by this `FnMut` closure
...
13 |                 Retval::Failure(make_closure(copy, handler))
   |                                                    ^^^^^^^ move occurs because `handler` has type `impl FnMut(E) + 'static`, which does not implement the `Copy` trait

I am out of ideas for solving this issue. Can you recommend an alternative approach?

You're returning a dyn FnMut type. FnMut can be called many times.

However, this closure returns Failure with the handler. It's the handler, not a copy of it. In Rust values are uniquely owned, meaning they exist only in one place at a time. So when you return handler, it stops existing inside your outer Box::new(move || { closure, and exists only in the returned value. But because the closure can be called many times, it could be forced to return the handler many times, and it has only one.

  1. You can return make_closure(copy, handler.clone()) if you mark the handler as a cloneable type (FnMut() + Clone).

  2. Another solution is to wrap the handler in Option: let handler = Some(handler) and then when returning it use handler.take().unwrap(). It will panic if the handler is returned twice.

  3. Alternatively, return impl FnOnce from the outer make_closure function, so that the closure owning the handler can be called only once, so it won't need multiple copies of the handler.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.