Kevin Meier
Kevin Meier

Reputation: 2590

Run background work and return "cancellation"-closure

I'm quite new to Rust, so this might be quite a beginner question: Assume I want to start some async action that runs in the background and with a function call I want to stop it. The desired API looks like this:

let stop = show_loadbar("loading some data...").await;
// load data...
stop().await;

My code:

pub fn show_loadbar(text: &str) -> Box<dyn FnOnce() -> Box<dyn Future<Output=()>>>
{
    let (sender, receiver) = channel::bounded::<()>(0);
    let display = task::spawn(async move {
        while receiver.try_recv().is_err() {
            // show loadbar: xxx.await;
        }
        // cleanup: yyy.await;
    });

    // return a function which stops the load bar
    Box::new(move || {
        Box::new(async {
            sender.send(()).await;
            display.await;
        })
    })
}

I played around quite a lot (creating a struct instead of a function and some combinations), but finally, I always get error like this:

error[E0373]: async block may outlive the current function, but it borrows `sender`, which is owned by the current function
  --> src/terminal/loading.rs:23:24
   |
23 |           Box::new(async {
   |  ________________________^
24 | |             sender.send(()).await;
   | |             ------ `sender` is borrowed here
25 | |             display.await;
26 | |         })
   | |_________^ may outlive borrowed value `sender`

Given the described API, is it even possible to implement the function like this in Rust? Independent of this, what is the Rust-way to do it? Maybe this interface is absolutely not how it should be done in Rust.

Thank you very much

Upvotes: 1

Views: 397

Answers (1)

user4815162342
user4815162342

Reputation: 154906

The immediate error you see can be fixed by changing async to async move, so that it captures sender by value instead of by reference. But trying tu use your code reveals futher issues:

  • you can't (and probably don't need to) await show_loadbar(), since it's not itself async.
  • pin the boxed future to be able to await it.
  • a bounded async_std channel cannot have the capacity of 0 (it panics if given 0);
  • handle the error returned by sender.send(), e.g. by unwrapping it.
  • (optionally) get rid of the outer box by returning impl FnOnce(...) instead of Box<dyn FnOnce(...)>.

With these taken into account, the code would look like this:

pub fn show_loadbar(_text: &str) -> impl FnOnce() -> Pin<Box<dyn Future<Output = ()>>> {
    let (sender, receiver) = channel::bounded::<()>(1);
    let display = task::spawn(async move {
        while receiver.try_recv().is_err() {
            // show loadbar: xxx.await;
        }
        // cleanup: yyy.await;
    });

    // return a function which stops the load bar
    || {
        Box::pin(async move {
            sender.send(()).await.unwrap();
            display.await;
        })
    }
}

// usage example:
async fn run() {
    let cancel = show_loadbar("xxx");
    task::sleep(Duration::from_secs(1)).await;
    cancel().await;
}

fn main() {
    task::block_on(run());
}

Upvotes: 2

Related Questions