struct Foo;
impl !Send for Foo {}
let _: impl Send = || {
let guard = Foo;
drop(guard);
yield;
};
(full playground) fails with
error[E0277]: `Foo` cannot be sent between threads safely
--> src/main.rs:14:12
|
14 | let _: impl Send = || {
| ^^^^^^^^^ `Foo` cannot be sent between threads safely
|
= help: within `[generator@src/main.rs:14:24: 18:6 {Foo, ()}]`, the trait `std::marker::Send` is not implemented for `Foo`
= note: required because it appears within the type `{Foo, ()}`
= note: required because it appears within the type `[generator@src/main.rs:14:24: 18:6 {Foo, ()}]`
The guard should be dead and deallocated before the yield point so shouldn't appear in the generator type and affect the Sendness. Wrapping the guard in a new scope before the yield avoids this (included in the playground). First noticed in relation to async functions on u.rl.o.
I'd like to work on this.
This is going to be tricky.
The error is occurring due to the computed generator witness type including Foo. This is done during initial type checking, before any MIR has been generated.
Making this work would require type resolution to depend on the results of NLL. Specifically, the computed generator witness type would have to depend on which locals are computed to be live during mir-borrowck.
@Zoxc @nikomatsakis: Thoughts?
My plan for this is just to generate MIR for just the generator during type checking and then do the analysis on MIR. Currently that isn't very feasible given the current compiler structure.
cc @rust-lang/lang, are we 100% sure we want to support this? The implication is there is going to be a "sharp edge" here when drops for certain locals move around relative to yield points (or vice versa).
It's also possible to work around it by turning your scope into a function call (possibly a closure that is immediately called).
You don't need a function call, just adding an actual scope around the variable that's dropped between yields is enough (this is included in the playground):
let _: impl Send = || {
{
let guard = Foo;
drop(guard);
}
yield;
};
It makes sense to me why this is as it is and the solution could be just improved diagnostics telling users to add these extra scopes, it just seems like an unnecessary pain point for async functions.
After being reminded of this I realised this isn't really about "dropping" variables, it's about moving the variables out of the generator, any function that takes ownership should cause the variable to no longer be alive in the generator (this is the same thing since drop is just a trivial function to move the variable out, but being explicit about it might help others like me that didn't instantly make the connection):
struct Foo;
impl !Send for Foo {}
fn use_foo(_: Foo) {}
let _: impl Send = || {
let guard = Foo;
use_foo(guard);
yield;
};
Another twist on the same problem might be using something like ManuallyDrop:
struct Foo;
impl !Send for Foo {}
let _: impl Send = || {
let guard = ManuallyDrop::new(Foo);
yield;
};
Marking as AsyncAwait-OnDeck - this error can be confusing, and it may not be obvious how to work around it.
What exactly is the bug here? To improve error report, or to do more precise drops? If the latter, that's a tricky problem indeed, but also duplicated by other issues.
The way I read the issue, it is about tracking drops more precisely. (What issues duplicate this?)
We should open a separate issue to track improving the error message.
@tmandry I'm not sure what issue is the "best duplicate" but I think we've been using https://github.com/rust-lang/rust/issues/57017 as a kind of stand-in for "more precise generator captures". It'd probably be good to create a generalized tracking issue that dives into the different sorts of cases, since it doesn't look like we're likely to get a generalized fix in the near-ish term.
Most helpful comment
You don't need a function call, just adding an actual scope around the variable that's dropped between yields is enough (this is included in the playground):
It makes sense to me why this is as it is and the solution could be just improved diagnostics telling users to add these extra scopes, it just seems like an unnecessary pain point for async functions.