Reputation: 262
I encountered a strange error. Here's a MRE:
async fn do_nothing() { }
async fn use_dyn_read() {
let read: &dyn std::io::Read = & (&[0 as u8; 1] as &[u8]);
do_nothing().await;
}
#[tokio::main]
fn main() {
tokio::spawn(use_dyn_read());
}
When I compile I get an error with this message:
error: future cannot be sent between threads safely
--> src/main.rs:10:18
|
10 | tokio::spawn(use_dyn_read());
| ^^^^^^^^^^^^^^ future returned by `use_dyn_read` is not `Send`
|
= help: the trait `Sync` is not implemented for `dyn std::io::Read`
note: future is not `Send` as this value is used across an await
--> src/main.rs:5:17
|
4 | let read: &dyn std::io::Read = & (&[0 as u8; 1] as &[u8]);
| ---- has type `&dyn std::io::Read` which is not `Send`
5 | do_nothing().await;
| ^^^^^^ await occurs here, with `read` maybe used later
6 | }
| - `read` is later dropped here
note: required by a bound in `tokio::spawn`
--> /home/jrpear/.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`
future is not 'Send' as this value is used across an await
means that the type of local variables in an async function affect the type of its future.
However, some non- Edit: I was wrong, Send
types don't cause this error. Using a Cell
instead of a &dyn Read
doesn't cause this error.Cell
is Send
(but not Sync
)
What's going on here?
Upvotes: 1
Views: 995
Reputation: 60447
Essentially, any local variable that is in-scope before and after an .await
must be able to be persisted in the generated Future
and will therefore affect its traits like Send
and Sync
. Here are some examples that demonstrate this:
struct IsSend;
struct NotSend(*const ()); // pointers are not Send
async fn is_send() {
// This local variable is in scope across an await-point and is Send, so the
// Future returned by this async function can also be Send.
let _is = IsSend;
futures::future::ready(()).await;
}
async fn not_send() {
// This local variable is in scope across an await-point and is not Send, so
// the Future returned by this async function can not be Send.
let _not = NotSend(&());
futures::future::ready(()).await;
}
async fn is_also_send() {
{
// This local variable is not Send but is not in scope across an await-
// point, so Future returned by this async function can still be Send.
let _not = NotSend(&());
}
futures::future::ready(()).await;
}
#[tokio::main]
async fn main() {
tokio::spawn(is_send());
// this does not compile since tokio::spawn requires Send
// tokio::spawn(not_send());
tokio::spawn(is_also_send());
}
The Cell
type does implement Send
(as long as its inner value is Send
).
One thing that's tricky to remember is that references only implement Send
if their referee implements Sync
. In essence, a type being Sync
means that it can be accessed from a different thread. So if it is Sync
then its reference is Send
since it can be sent to another thread and still refer to the original.
Trait objects are comparatively easier to understand, they only express the traits that they advertise. So a dyn Read
does not implement Send
or Sync
since it doesn't say it does. So that coupled with our reference rules means that read
needs to be &dyn Read + Sync
in order to implement Send
so it can be used across the .await
while allowing the Future
to implement Send
.
Upvotes: 1
Reputation: 43842
dyn
types implement only the traits that are explicitly written in them (and their supertraits). You don't get to automatically take advantage of trait implementations of the concrete type you're using, which is what happens in Rust code that doesn't use dyn
. Instead, you have to pick the traits that will be exposed (and, equally, the traits that will be required of the concrete type).
You wrote dyn Read
, so it doesn't implement Sync
. Change it to dyn Read + Sync
and the program compiles.
async fn use_dyn_read() {
let read: &(dyn std::io::Read + Sync) = & (&[0 as u8; 1] as &[u8]);
do_nothing().await;
}
(The reason we're writing Sync
, not Send
, is because we need the future to be Send
, so we need the &dyn ...
to be Send
, and the rule for a immutable reference being Send
is that the referent must be Sync
— because an immutable reference grants shared access. However, in order to actually use this Read
, you'll want to change the &
to &mut
, at which point you'll need + Send
instead of + Sync
.)
Upvotes: 1