Reputation: 10499
This is the code that raises a compiler error suggesting that MyFn
should be restricted with a Sync
bound:
use std::future::Future;
fn spawn<MyFn, Out>(func: MyFn)
where
Out: Future + Send + 'static,
MyFn: Fn() -> Out + Send + 'static,
{
tokio::spawn(call_func(func));
}
async fn call_func<MyFn, Out>(func: MyFn)
where
MyFn: Fn() -> Out,
Out: Future,
{
func().await;
func().await;
}
This raises the following error:
Compiling playground v0.0.1 (/playground)
error: future cannot be sent between threads safely
--> src/main.rs:8:18
|
8 | tokio::spawn(call_func(func));
| ^^^^^^^^^^^^^^^ future returned by `call_func` is not `Send`
|
note: future is not `Send` as this value is used across an await
--> src/main.rs:16:11
|
16 | func().await;
| ---- ^^^^^^ await occurs here, with `func` maybe used later
| |
| has type `&MyFn` which is not `Send`
note: `func` is later dropped here
--> src/main.rs:16:17
|
16 | func().await;
| ^
help: consider moving this into a `let` binding to create a shorter lived borrow
--> src/main.rs:16:5
|
16 | func().await;
| ^^^^^^
note: required by a bound in `tokio::spawn`
--> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.21.2/src/task/spawn.rs:127:21
|
127 | T: Future + Send + 'static,
| ^^^^ required by this bound in `tokio::spawn`
help: consider dereferencing here
|
8 | tokio::spawn(*call_func(func));
| +
help: consider further restricting this bound
|
6 | MyFn: Fn() -> Out + Send + 'static + std::marker::Sync,
| +++++++++++++++++++
error: could not compile `playground` due to previous error
Why exaclty does MyFn
need to be restricted using the Sync
bound, when all that tokio::spawn requires is the following?
pub fn spawn<T>(future: T) -> JoinHandle<T::Output>ⓘ
where
T: Future + Send + 'static,
T::Output: Send + 'static,
Upvotes: 1
Views: 412
Reputation: 43753
In this part of your code:
async fn call_func<MyFn, Out>(func: MyFn)
where
MyFn: Fn() -> Out,
Out: Future,
{
func().await;
func().await;
}
you are calling func
twice. The way this is possible — why it doesn't violate Rust's move semantics even though MyFn
does not have a Copy
bound — is that func
is being called by &
reference, since it is a Fn
and the definition of Fn
is that you can call a function if you have &
to it. Then, since there is an await
in the middle, this code might move between threads. So, references to the value func
is being used from two different threads, so &MyFn: Send
is required, so MyFn: Sync
is required.
Here's one trick to solve the problem: declare the function as FnMut
instead of Fn
. This means that a &mut
reference is understood to be required to call the function, and since &mut
references are unique they don't require Send
of their referent. These changes compile successfully:
fn spawn<MyFn, Out>(func: MyFn)
where
Out: Future + Send + 'static,
MyFn: FnMut() -> Out + Send + 'static,
{
tokio::spawn(call_func(func));
}
async fn call_func<MyFn, Out>(mut func: MyFn)
where
MyFn: FnMut() -> Out,
Out: Future,
{
func().await;
func().await;
}
In principle, the compiler could observe that the internal &MyFn
never needs to move between threads, and do the equivalent of my FnMut
rewrite internally (every Fn
is also a FnMut
), but evidently it doesn't.
Upvotes: 2