How to abstract over a generic async runtime spawn function?

I am implementing a library. I'd like the library not to depend on a specific asynchronous runtime.

I have a partially-sealed MySpawn trait with an asynchronous spawn method:

pub trait MySpawn {
    async fn spawn<F, Fut>(&mut self, f: F) -> u32
    where
        F: FnOnce() -> Fut,
        Fut: Future<Output = ()>;
}

The implementations of the spawn method need to:

  1. mutate self; call asynchronous functions;
  2. call f and obtain a Fut future;
  3. spawn a task onto a runtime; the task awaits the Fut future and does other operations;
  4. mutate self; call asynchronous functions;
  5. return a value (suppose u32).

The trait is partially sealed so that the user can only decide how to perform point 3.

Is is possible to modify the trait so that point 3 is runtime agnostic?

The first attempted solution I thought is to return the task and ask the caller to spawn it, but this won't work because the method would still need to do 4 and 5.

The second attempted solution I thought is to modify the method adding an S closure that takes the task as parameter; the implementation of S would then contain a tokio::spawn call (or a call to some other runtime) with the task as argument. This won't work, because we can't name the type of the task.

Playground of the second attempt

Of course, there is the inconvenience that the user could forget to call the runtime in the closure.
We can fixed this by requiring the closure to return a type that is not directly constructible by the user but that can be only obtained by the Fut, like this.

The second approach would require a higher-kinded type over the future type. The Fn family of traits don't allow that, but you can make your own trait: Playground

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.