Confusion on async closure/block

I have use_future and it takes impl FnMut() -> F + 'static. With the move || async move {} as the closure, I don't have a good way to deal with the move.

#![deny(elided_lifetimes_in_paths)]
#![allow(unused_variables)]

// mimic https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html
fn use_future<F>(_future: impl FnMut() -> F + 'static)
where
    F: std::future::Future + 'static,
{
    ()
}

#[derive(Clone)]
struct Props {
    id: String,
    x: Vec<i64>,
    y: Vec<i64>,
}

fn component(props: Props) {
    use_future(move || async move {
        let plot = Plot::new(Bar::new(props.x, props.y));

        new_plot(&props.id, &plot).await;
    });

    // I am still in use in the HTML later
    let _ = &props;
}

struct Plot {
    bar: Bar,
}

impl Plot {
    fn new(bar: Bar) -> Self {
        Self { bar }
    }
}

struct Bar {
    x: Vec<i64>,
    y: Vec<i64>,
}

impl Bar {
    fn new(x: Vec<i64>, y: Vec<i64>) -> Self {
        Self { x, y }
    }
}

async fn new_plot(id: &str, plot: &Plot) {
    todo!()
}


error[E0507]: cannot move out of `props.id`, a captured variable in an `FnMut` closure
  --> src/lib.rs:20:24
   |
19 |   fn component(props: Props) {
   |                ----- captured outer variable
20 |       use_future(move || async move {
   |  ________________-------_^
   | |                |
   | |                captured by this `FnMut` closure
21 | |         let plot = Plot::new(Bar::new(props.x, props.y));
22 | |
23 | |         new_plot(&props.id, &plot).await;
   | |                   --------
   | |                   |
   | |                   variable moved due to use in coroutine
   | |                   move occurs because `props.id` has type `String`, which does not implement the `Copy` trait
24 | |     });
   | |_____^ `props.id` is moved here
   |
help: clone the value before moving it into the closure
   |
20 ~     use_future(move || {
21 +     let value = props.id.clone();
22 ~     async move {
23 |         let plot = Plot::new(Bar::new(props.x, props.y));
24 | 
25 ~         new_plot(&value, &plot).await;
26 ~     }
27 ~     });
   |

error[E0507]: cannot move out of `props.x`, a captured variable in an `FnMut` closure
  --> src/lib.rs:20:24
   |
19 |   fn component(props: Props) {
   |                ----- captured outer variable
20 |       use_future(move || async move {
   |  ________________-------_^
   | |                |
   | |                captured by this `FnMut` closure
21 | |         let plot = Plot::new(Bar::new(props.x, props.y));
   | |                                       -------
   | |                                       |
   | |                                       variable moved due to use in coroutine
   | |                                       move occurs because `props.x` has type `Vec<i64>`, which does not implement the `Copy` trait
22 | |
23 | |         new_plot(&props.id, &plot).await;
24 | |     });
   | |_____^ `props.x` is moved here
   |
help: clone the value before moving it into the closure
   |
20 ~     use_future(move || {
21 +     let value = props.x.clone();
22 ~     async move {
23 ~         let plot = Plot::new(Bar::new(value, props.y));
24 | 
25 |         new_plot(&props.id, &plot).await;
26 ~     }
27 ~     });
   |

error[E0507]: cannot move out of `props.y`, a captured variable in an `FnMut` closure
  --> src/lib.rs:20:24
   |
19 |   fn component(props: Props) {
   |                ----- captured outer variable
20 |       use_future(move || async move {
   |  ________________-------_^
   | |                |
   | |                captured by this `FnMut` closure
21 | |         let plot = Plot::new(Bar::new(props.x, props.y));
   | |                                                -------
   | |                                                |
   | |                                                variable moved due to use in coroutine
   | |                                                move occurs because `props.y` has type `Vec<i64>`, which does not implement the `Copy` trait
22 | |
23 | |         new_plot(&props.id, &plot).await;
24 | |     });
   | |_____^ `props.y` is moved here
   |
help: clone the value before moving it into the closure
   |
20 ~     use_future(move || {
21 +     let value = props.y.clone();
22 ~     async move {
23 ~         let plot = Plot::new(Bar::new(props.x, value));
24 | 
25 |         new_plot(&props.id, &plot).await;
26 ~     }
27 ~     });
   |

error[E0382]: borrow of partially moved value: `props`
  --> src/lib.rs:27:13
   |
20 |     use_future(move || async move {
   |                ------- value partially moved into closure here
21 |         let plot = Plot::new(Bar::new(props.x, props.y));
   |                                                ------- variable partially moved due to use in closure
...
27 |     let _ = &props;
   |             ^^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `props.y` has type `Vec<i64>`, which does not implement the `Copy` trait

My Workaround

To fix/workaround the error, I have to clone twice and I understand vaguely the reason for it.

  1. First cloning is to move them(id1, x1, y1) to the closure
  2. Second cloning is to move (id2, x2, y2) to the async block
    let id1 = props.id.clone();
    let x1 = props.x.clone();
    let y1 = props.y.clone();

    use_future(move || {
        let id2 = id1.clone();
        let x2 = x.clone();
        let y2 = y.clone();

        async move {
            let plot = Plot::new(Bar::new(x2, y2));

            new_plot(&id2, &plot).await;
        }
    });

to make it easier to understand what I'm talking about, let's first rewrite type signature of fn use_future() like this:

fn use_future<F, Fut>(f: F)
where
    F: FnMut() -> Fut + 'static,
    Fut: Future + 'static,
{
    //...
}

this is due to the bounds on the closure type F, i.e. F: FnMut() + 'static; if you can remove the static bound, this clone (and the move keyword for the closure) can be eliminated. also, if you can change the FnMut to FnOnce, you may get rid of the second clone (more on this later). but I doubt it you can, as it will change the semantics and capabilities of the API.

this is due to the bounds on the return type of the closure, i.e. Fut: Future + 'static, and combined with the inferred captures from the code within the async block. for example, the Bar::new() function takes arguments by value, so the async block must be move. but because the variable (part of the closure captures) is owned by the FnMut closure, you only get a &mut reference to it, that's why you need to make the second clone (the clone is not part of the capture and thus is owned by the call_mut() and can be moved).

all in all, if you cannot change the API, then the two clones are unavoidable. I would not worry about them. however, if these clones turn out to cause performance issues (measure, don't just guess), and the data are immutable (thus they can be shared), consider use Arc so you don't clone all the data. if the entire dataset cannot be shared, you might need to redesign your data structure to allow fine grained access control.

1 Like

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.