-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Open
Listed in
Labels
A-async-awaitArea: Async & AwaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.Async-await issues that have been triaged during a working group meeting.C-feature-requestCategory: A feature request, i.e: not implemented / a PR.Category: A feature request, i.e: not implemented / a PR.T-langRelevant to the language teamRelevant to the language team
Description
Here's an example which I think should compile, but which doesn't (cargo 1.39.0-nightly 3f700ec43 2019-08-19):
#![feature(async_await)]
fn require_send<T: Send>(_: T) {}
struct NonSendStruct { _ptr: *mut () }
async fn my_future() {
let nonsend = NonSendStruct { _ptr: &mut () };
async {}.await;
}
fn main() {
require_send(my_future()); // error: `*mut ()` cannot be sent between threads safely
}
The error is "*mut ()
cannot be sent between threads safely", which is to say my_future()
is !Send
. I'm surprised by that, because the nonsend
variable is never used after the .await
point, and it's not Drop
. Some other notes:
- Adding a
drop(nonsend)
call after thelet nonsend = ...
line doesn't help. - This does compile if swap the two lines in
my_future
. That is, ifnonsend
is created after the.await
point, the future is stillSend
.
Are there any future plans to have the rustc look more closely at which locals do or do not need to be stored across an .await
?
clavin, CypherNaught-0x, GoldsteinE, kornelski, andersk and 11 more
Metadata
Metadata
Assignees
Labels
A-async-awaitArea: Async & AwaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.Async-await issues that have been triaged during a working group meeting.C-feature-requestCategory: A feature request, i.e: not implemented / a PR.Category: A feature request, i.e: not implemented / a PR.T-langRelevant to the language teamRelevant to the language team
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
Nemo157 commentedon Aug 21, 2019
Even if
NonSendStruct
doesn't have an explicit implementation ofDrop
it's still dropped at the end of scope so is alive across the yield point.Having rustc try and infer shorter scopes to kill variables before yield points sounds like it might lead to surprising situations when you try and use a variable that appears live which will suddenly make your future
!Send
, or adding aDrop
implementation to the variable or any of its fields. It would be good for it to do so as an optimization when it doesn't visibly affect the execution, but it doesn't seem like that should affect the type checking.See also #57478 about explicitly dropping the variable before the yield point.
oconnor663 commentedon Aug 21, 2019
Thanks for clarifying for me. While I was playing with this I was thinking about the interaction between
Drop
and non-lexical lifetimes. Like you can have two consecutive uses of a struct that mutably borrow the same variable, so long as the first usage is "dead" before the second starts. But then that code can break if the struct or any of its members gains aDrop
implementation in the future, or if you add a new usage of the first instance after the second instance has been constructed.In my mind, borrow checking and "
Send
checking" (whatever that's called) could have worked similarly, using the same "liveness" concept. Though I have no idea what's practical in the compiler, and the issue you linked makes it sound like this might be impractical. But so you see what I mean about how it feels like "liveness" could apply to both?oconnor663 commentedon Aug 21, 2019
In particular, I found it surprising that "use a nested scope" works as a solution. To me, non-lexical lifetimes were sort of about how "we don't need to use nested scopes anymore," and it seems like a shame to regress on that a little.
But then again there are a bunch of complexities around NLL that I can't think of an equivalent for here. Like
Vec
is marked#[may_dangle]
for its contents, such that it doesn't have to "keep them alive" even though it hasn't been destructed yet. I don't know what the equivalent would be forSend
-ness. If aVec<T>
is "alive but never used again" across a yield point, andT
isn'tSend
but also doesn't needDrop
, do we feel like this future is morally stillSend
? I think it could be. But I don't know if#[may_dangle]
per se can be reused to indicate that, or if the language would need some additional feature to express it.RustyYato commentedon Aug 21, 2019
@oconnor663 NLL has nothing to do with drop order, so it won't help there. NLL only changed lifetime analysis, which will allow strictly more programs, but won't change the behavior of old programs.
oconnor663 commentedon Aug 21, 2019
@KrishnaSannasi for sure, I was only bringing up NLL as a metaphor for what might be possible/desirable to do about the
Send
/Sync
/Drop
-iness of async functions. It seems like the reasoning we do today around "Is this borrow alive?" could be very similar to reasoning we might want to do around "Does this object cross an.await
boundary and therefore 'infect' the async function?":Drop
at all could be considered not to cross.await
boundaries after their last mention.Drop
for their fields but don't implementDrop
themselves could have.await
boundary crossing evaluated independently for each field. (I believe NLL does something similar for lifetimes? If theDrop
field is independent of the containing struct's lifetime parameters, those lifetimes are not extended to end of scope.)Drop
with#[may_dangle]
on their contents could be treated as though the contents did not cross any.await
boundaries after the container's last mention, if the contents aren'tDrop
. (And this could interact with the second case, if only a sub-field of the contents implementsDrop
.)RustyYato commentedon Aug 21, 2019
Ok, that looks like it could work, altough I'm not sure if this would make undesirable changes to drop order (haven't really thought about drop order too much before, so I don't know much about the details). Someone else can evaulate that.
oconnor663 commentedon Aug 21, 2019
I guess I'd frame it as "looking very closely at the existing drop order and figuring out whether a
!Send
object (or field) really lives past an await point."8 remaining items
nikomatsakis commentedon Apr 1, 2021
Added to #69663
ibraheemdev commentedon Jul 16, 2021
I was recently surprised by this not compiling:
But this working just fine:
Even more surprisingly, this doesn't work either:
kaimast commentedon Apr 8, 2023
Any hope this will get fixed soon?
dingxiangfei2009 commentedon Mar 1, 2024
As far as I know, none of the examples here are erring out. However, I suspect that when the non-
Send
locals have non-trivial destructors, the infection exists unless explicitdrop(..)
s are inserted, of course.So... can we check this off the list this time? Or are we still missing something? 🤔
drakedevel commentedon Jul 12, 2024
Still missing something, unfortunately. As of 1.79.0 and on Nightly 2024-07-10, this compiles:
But this variant of
affected_fn
does not, with the only difference being the removal of the explicit scope:Nor does this one, which gets rid of the local entirely, and provokes a bizarre "with
recv.borrow()
maybe used later" diagnostic:Changing that line to
{ let _ = &*recv.borrow(); }
allows it to compile, as does getting rid of thelet _ =
(but that provokes a warning suggesting the version that doesn't compile).LeHoangLong commentedon Nov 16, 2024
This is very needed in case of match expression and async, especially with non async libraries like
syn
Example:
await
point #136349