Creating a Box<dyn Future> for WatchExec Async

I'm trying to use watchexec::Watchexec::new_async to set up a new CLI program for watching and responding to file changes. I need to do something very specific, so I'm writing a CLI rather than just using the watchexec binary directly.

Links

I have a type Runtime, which is bound as an Arc<tokio::sync::Mutex<Runtime>>, that contains read/write state for the application and which provides a function like this:

use watchexec::Watchexec;
use watchexec::action::ActionHandler;

#[derive(Debug)]
struct Runtime {
    state: usize,
}

impl Runtime {
    pub async fn on_event(&mut self, action: ActionHandler) -> ActionHandler {
        tracing::info!("Event received");
        action
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // create a send/sync ref to the runtime
    let rt = Arc::new(tokio::sync::Mutex::new(Runtime { state: 0 }));

    // fails to compile
    let wx = Watchexec::new_async(async move |action| {
        rt.lock().on_event(action).await
    });

    wx.config.pathset(std::path::PathBuf::from("./watched"));
    wx.main().await??;

    Ok(())
}

This fails to compile for a number of reasons, but most importantly, the method signature for Watchexec::new_async:

fn new_async(
    action_handler: impl Fn(ActionHandler) -> Box<dyn Future<Output=ActionHandler> + Send + Sync> + Send + Sync + 'static
) -> Result<Arc<Self>, CriticalError>

The issues:

  1. First, async closures are still not in stable so I can't seem to just use async move |action| ....
  2. It needs a Box<dyn Future<...>>, and when I try to simply use futures::FutureExt::boxed, this returns a BoxFuture, not a Box<dyn Future<...>>
  3. I have no idea how to enforce Send + Sync + 'static and the other constraints on this.

How can I adapt my code to fit this API? Ideally, I'd just like it to invoke Runtime::on_event on an instantiated Runtime object to make things easiest to reason about.

The signature impl Fn(ActionHandler) -> Box<dyn Future<Output=ActionHandler> + Send + Sync> implies that the closure has to call Box::new in the synchronous context. Async closures wouldn’t work here even if they were stable.

You want something like this, which uses a plain async block to construct a future, then boxes it:

Watchexec::new_async(move |action| Box::new(async move { … }))
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.