maor10
maor10

Reputation: 1784

How do I wrap a closure which returns a Future, without it being Sync?

I have the following code, in an attempt to wrap a function that returns a future:

use std::future::Future;
use std::pin::Pin;

pub type BoxedOperation = Box<dyn Fn() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;

fn create_func<L, R>(func: L) -> BoxedOperation
where L: Fn() -> R + Clone + Send + 'static,
      R: Future<Output = ()> + Send + 'static
{
    Box::new(move || {
        let func = func.clone();
        Box::pin(async move {
            // My logic before
            (func)().await; // In the future, func will receive params
            // my logic after
        })
    })
}

This code doesn't compile-

  --> src/main.rs:14:9
   |
14 | /         Box::pin(async move {
15 | |             (func)().await;
16 | |         })
   | |__________^ future created by async block is not `Send`
   |
note: future is not `Send` as this value is used across an await
  --> src/main.rs:15:21
   |
15 |             (func)().await;
   |             ------  ^^^^^^ await occurs here, with `(func)` maybe used later
   |             |
   |             has type `&L` which is not `Send`
note: `(func)` is later dropped here
  --> src/main.rs:15:27
   |
15 |             (func)().await;
   |                           ^
help: consider moving this into a `let` binding to create a shorter lived borrow
  --> src/main.rs:15:13
   |
15 |             (func)().await;
   |             ^^^^^^^^
   = note: required for the cast from `impl Future<Output = ()>` to the object type `dyn Future<Output = ()> + Send`
help: consider further restricting this bound
   |
9  | where L: Fn() -> R + Clone + Send + 'static + std::marker::Sync,
   |   
                                          +++++++++++++++++++

Now it's critical for the BoxedOperation here to return a future that is Send so I can tokio::spawn it later on, and I also don't want it to be Sync as that would require all everything used in func to be Sync as well. I don't really understand why this isn't Send in the first place.

Upvotes: 3

Views: 1097

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 71310

This is a tricky case.

When you call func, the compiler uses Fn::call() that takes self by reference. So the compiler borrows func, creating &L, and calls this type. But as generator/async functions captures are not precise, this type ends up being included in the resulting future even though it is not needed, and so the resulting future requires &L: Send -> L: Sync in order to be Send.

To fix the problem, you can force the compiler to drop the borrow immediately before awaiting the returned future:

fn create_func<L, R>(func: L) -> BoxedOperation
where
    L: Fn() -> R + Clone + Send + 'static,
    R: Future<Output = ()> + Send + 'static,
{
    Box::new(move || {
        let func = func.clone();
        Box::pin(async move {
            let fut = { func() }; // Notice the block.
            fut.await;
        })
    })
}

Upvotes: 4

Related Questions