Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent move behavior in closure with async block #81653

Open
vultix opened this issue Feb 2, 2021 · 6 comments
Open

Inconsistent move behavior in closure with async block #81653

vultix opened this issue Feb 2, 2021 · 6 comments
Labels
A-async-await Area: Async & Await A-closures Area: Closures (`|…| { … }`) AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug.

Comments

@vultix
Copy link

vultix commented Feb 2, 2021

This closure fails to compile:

|val: i32| async {
    dbg!(val)
};

error[E0373]: async block may outlive the current function, but it borrows `val`, which is owned by the current function

I'd expect that this would compile - val should be moved into the async closure.

This is inconsistent with the following cases where it compiles (playground link):

  • If you change the type of val to a !Copy type (such as String)
  • If you get rid of the async keyword
  • If you don't use a closure and just have an async block capture a Copy variable

This has a serious drawback in that it forces you to use async move even when you can't move everything into the async block.

Workaround

This is super unergonomic, but as a workaround you can surround the copy type with a NonCopy newtype.

struct NonCopy(i32);
|val: NonCopy| async {
    dbg!{val.0}
};

Meta

This happens on Stable, Nightly, and Beta

rustc --version --verbose:

rustc 1.50.0-nightly (1c389ffef 2020-11-24)
binary: rustc
commit-hash: 1c389ffeff814726dec325f0f2b0c99107df2673
commit-date: 2020-11-24
host: x86_64-apple-darwin
release: 1.50.0-nightly

@vultix vultix added the C-bug Category: This is a bug. label Feb 2, 2021
@jonas-schievink
Copy link
Contributor

This is super unergonomic, but as a workaround you can surround the copy type with a NonCopy newtype.

Just async move { ... } is enough too

@jonas-schievink jonas-schievink added A-async-await Area: Async & Await A-closures Area: Closures (`|…| { … }`) labels Feb 2, 2021
@vultix
Copy link
Author

vultix commented Feb 2, 2021

Just async move { ... } is enough too

Here's an example of where async move isn't really an option (playground)

use futures::{Stream, StreamExt};

#[derive(Debug, Clone)]
struct Config {}

async fn bad(stream: impl Stream<Item = usize>, config: Config) {
    stream.for_each(|item| async {
        dbg!{item, &config};
    }).await;
}

This is almost the exact problem that I ran into the other day. I would expect this to compile - item is Copy and config is only referenced.

Adding async move to appease the Copy bug means that I can no longer take a reference to config. The only solution I found was to surround item in a NonCopy newtype

@tmandry
Copy link
Member

tmandry commented Feb 4, 2021

The difference between Copy and non-Copy types definitely seems unexpected. I hope this is something we can improve on.

@tmandry tmandry added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label Feb 4, 2021
@Sushisource
Copy link

Also ran into this just today - pretty frustrating to run into because there's not reason not to just... copy the copy type, right? I found this issue while I was about to make a new one. I have a slightly more elaborate example that boils down to largely the same thing as vultix's example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5bd544f234579b29974d1e62f6fe75ec

The workarounds are pretty painful here for something you'd expect to not be an issue

@ptrojahn
Copy link
Contributor

I think the issue here is that this isn't an async closure, but a closure with an async block.
The async block gets lowered to a closure itself, so if we use the arguments of the outer closure in the async block they get captured by reference if possible. NonCopy forces the async block to capture the argument by move.
The solution here would be to use async closures(#62290).
This isn't really a bug, but the error message when mixing up async || and || async should definitely be improved once async closures are fully implemented.

@eholk
Copy link
Contributor

eholk commented Oct 7, 2021

This sounds like the sort of thing that might have a fairly small fix and be a good starter issue. Maybe someone wants to write up some mentoring instructions for it?

@rustbot label E-needs-mentor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-closures Area: Closures (`|…| { … }`) AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug.
Projects
Status: On deck
Development

No branches or pull requests

8 participants