You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Unused arguments to async fn are dropped too early #54716
Unused arguments to async fn are not moved into the resulting generator so are dropped before the future runs, here's some example psuedo-code demonstrating this (full running playground example here):
Marking this as blocking stabilization of async-await. Obviously, changing the semantics here would alter the semantics of stable code. Moreover, there's a good case to be made that the current semantics are incorrect. @cramertj and I (along with a few others) discussed this in a recent meeting. Our conclusion was that we effectively wanted a desugaring like the following:
asyncfnfoo(<pattern> @ x:Type){}// becomesfnfoo(<pattern> @ x:Type){asyncmove{let <pattern> = x;}// <-- as you "exit" the async block}
where x is a kind of "fresh name" not visible to the user.
Some of the key test cases we want to consider would be:
async fn foo(_: Foo) -- the Foo value should be dropped after the future is "waited"
async fn foo(_x: Foo) -- same
This is quite analogous then to the behavior for fn foo(_: Foo) and friends -- basically after the "return" executes, the parameters are dropped (whereas current behavior drops before the body of the function even begins (i.e., before the future is polled)).
Tagging as E-needs-mentor as we need to write up some notes on how to alter the desugaring here. One bit I can see being complex is that the HIR presently has no way to "name" a parameter apart from entering a pattern.
FWIW, I expect parameters to behave as a "magic" let «pat» = get_fn_param_from_calling_convention!(). This would mean that a parameter that is explicitly ignored (_: «type») would drop "immediately", as happens for let _ = «expr», and a "silenced unused" parameter (_«ident»: «type») would drop "late", as happens for let _«ident» = «expr».
Maybe the difference in behavior here is surprising, but we have it, and authors know to keep this in mind when holding an e.g. scope_guard. We have a warning for unused bindings that don't start with an underscore today; if the type has drop glue we can theoretically suggest using either depending on when the user wants the drop glue to run.
I definitely do not expect the addition of the use of a usable binding (that is, one that isn't _) to change drop order. I would consider it a misfeature if that happened. The _ pattern exists as a "trash can" to drop things you don't want anymore into. If I don't use that pattern, I'm ok with it sticking around (and will have an unused lint if it's not prefixed with an underscore).
(_: «type») would drop "immediately", as happens for let _ = «expr», and a "silenced unused" parameter (_«ident»: «type») would drop "late", as happens for let _«ident» = «expr».
This seems to solve all the performance questions for the price of users having to know that the two syntaxes don't behave the same. I must say I like it. There won't be any free cake whichever way we take this.
We discussed this at the last lang team meeting and decided to change the drop order of async fn to match the existing drop order used by non-async functions. One significant motivation was the converting of things like fn foo(_lock: Mutex<()>) { ... } and other similar "just for the Drop effects" arguments to async fn, and that users might be surprised and confused by the difference in behavior.
However, in the case that we're not able to come up with a satisfactory implementation prior to resolving the other blockers for async/await, stabilization, we will temporarily introduce a feature gate against unused non-Copy arguments to async fn to allow stabilization to move forward.
@cramertj Sorry if this is a noob question, but is the drop order for non-async functions: _ => drop early, _ident => drop late? And will that be consistent behaviour everywhere, with the user being able to opt into perf gain by using just _?
And will that be consistent behaviour everywhere, with the user being able to opt into perf gain by using just _?
No, the performance gain being discussed here is not achievable by using _ in argument position. _ bindings in argument position drop at the end of the function, not at the start (or, in this case, prior to the future even having started).
Would that be a viable future change (for sync and async) to put through Crater? (As iirc parameter drop order isn't actually specified and it would make it more consistent with let.)
It doesn't need to be, but it seems desirable on the surface.
@CAD97 That would be a breaking change to a pretty significant amount of existing code, a significant chunk of which is probably unsafe (e.g. functions using _: Mutex<()> as a guard, as I mentioned above).
Activity
nikomatsakis commentedon Mar 5, 2019
Marking this as blocking stabilization of async-await. Obviously, changing the semantics here would alter the semantics of stable code. Moreover, there's a good case to be made that the current semantics are incorrect. @cramertj and I (along with a few others) discussed this in a recent meeting. Our conclusion was that we effectively wanted a desugaring like the following:
where
xis a kind of "fresh name" not visible to the user.Some of the key test cases we want to consider would be:
async fn foo(_: Foo)-- theFoovalue should be dropped after the future is "waited"async fn foo(_x: Foo)-- sameThis is quite analogous then to the behavior for
fn foo(_: Foo)and friends -- basically after the "return" executes, the parameters are dropped (whereas current behavior drops before the body of the function even begins (i.e., before the future is polled)).nikomatsakis commentedon Mar 5, 2019
Tagging as E-needs-mentor as we need to write up some notes on how to alter the desugaring here. One bit I can see being complex is that the HIR presently has no way to "name" a parameter apart from entering a pattern.
cramertj commentedon Apr 2, 2019
(nominating for discussion in T-lang to resolve the desired behavior)
cramertj commentedon Apr 2, 2019
Summary of the current issues by @davidtwco can be found on Zulip.
pnkfelix commentedon Apr 4, 2019
blocks #50547
12 remaining items
CAD97 commentedon Apr 19, 2019
FWIW, I expect parameters to behave as a "magic"
let «pat» = get_fn_param_from_calling_convention!(). This would mean that a parameter that is explicitly ignored (_: «type») would drop "immediately", as happens forlet _ = «expr», and a "silenced unused" parameter (_«ident»: «type») would drop "late", as happens forlet _«ident» = «expr».Maybe the difference in behavior here is surprising, but we have it, and authors know to keep this in mind when holding an e.g.
scope_guard. We have a warning for unused bindings that don't start with an underscore today; if the type has drop glue we can theoretically suggest using either depending on when the user wants the drop glue to run.I definitely do not expect the addition of the use of a usable binding (that is, one that isn't
_) to change drop order. I would consider it a misfeature if that happened. The_pattern exists as a "trash can" to drop things you don't want anymore into. If I don't use that pattern, I'm ok with it sticking around (and will have an unused lint if it's not prefixed with an underscore).najamelan commentedon Apr 19, 2019
This seems to solve all the performance questions for the price of users having to know that the two syntaxes don't behave the same. I must say I like it. There won't be any free cake whichever way we take this.
cramertj commentedon Apr 19, 2019
We discussed this at the last lang team meeting and decided to change the drop order of
async fnto match the existing drop order used by non-asyncfunctions. One significant motivation was the converting of things likefn foo(_lock: Mutex<()>) { ... }and other similar "just for theDropeffects" arguments toasync fn, and that users might be surprised and confused by the difference in behavior.However, in the case that we're not able to come up with a satisfactory implementation prior to resolving the other blockers for
async/await, stabilization, we will temporarily introduce a feature gate against unused non-Copyarguments toasync fnto allow stabilization to move forward.najamelan commentedon Apr 19, 2019
@cramertj Sorry if this is a noob question, but is the drop order for non-async functions:
_=> drop early,_ident=> drop late? And will that be consistent behaviour everywhere, with the user being able to opt into perf gain by using just_?cramertj commentedon Apr 19, 2019
No, the performance gain being discussed here is not achievable by using
_in argument position._bindings in argument position drop at the end of the function, not at the start (or, in this case, prior to the future even having started).CAD97 commentedon Apr 19, 2019
Would that be a viable future change (for sync and async) to put through Crater? (As iirc parameter drop order isn't actually specified and it would make it more consistent with
let.)It doesn't need to be, but it seems desirable on the surface.
cramertj commentedon Apr 19, 2019
@CAD97 That would be a breaking change to a pretty significant amount of existing code, a significant chunk of which is probably
unsafe(e.g. functions using_: Mutex<()>as a guard, as I mentioned above).comex commentedon Apr 20, 2019
OTOH, if the behavior were changed in an edition, it would be a relatively straightforward automatic migration...
davidtwco commentedon Apr 21, 2019
Assigning myself again since I have an open PR for this.
Rollup merge of rust-lang#59823 - davidtwco:issue-54716, r=cramertj
Rollup merge of rust-lang#59823 - davidtwco:issue-54716, r=cramertj
Rollup merge of rust-lang#59823 - davidtwco:issue-54716, r=cramertj