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

Confusing extension of lifetime of reference causes lack of Send in Future #95412

Open
ijackson opened this issue Mar 28, 2022 · 4 comments
Open
Labels
A-async-await Area: Async & Await A-lifetimes Area: Lifetimes / regions AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug.

Comments

@ijackson
Copy link
Contributor

I tried this code:

use std::sync::mpsc;
use std::future::Future;
use std::pin::Pin;

async fn awaitpoint(_: ()) { }

async fn failure() {
    let (_, rx) = mpsc::channel::<()>();

    while let Ok(event) = rx.recv() {
        awaitpoint(event).await;
    }
}

fn main() {
    let _: Pin<Box<dyn Future<Output=()> + Send>> = Box::pin(
        async {
            failure().await;
        }
    );
}

Ideally, it would compile. (Obviously it doesn't make much sense, semantically.)

Instead, it produces this error:

error: future cannot be sent between threads safely
  --> src/main.rs:16:53
   |
16 |       let _: Pin<Box<dyn Future<Output=()> + Send>> = Box::pin(
   |  _____________________________________________________^
17 | |         async {
18 | |             failure().await;
19 | |         }
20 | |     );
   | |_____^ future created by async block is not `Send`
   |
   = help: the trait `Sync` is not implemented for `std::sync::mpsc::Receiver<()>`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:11:26
   |
10 |     while let Ok(event) = rx.recv() {
   |                           -- has type `&std::sync::mpsc::Receiver<()>` which is not `Send`
11 |         awaitpoint(event).await;
   |                          ^^^^^^ await occurs here, with `rx` maybe used later
12 |     }
   |     - `rx` is later dropped here
help: consider moving this into a `let` binding to create a shorter lived borrow
  --> src/main.rs:10:27
   |
10 |     while let Ok(event) = rx.recv() {
   |                           ^^^^^^^^^
   = note: required for the cast to the object type `dyn Future<Output = ()> + Send`

The error message leads me to conclude that the reference &rx which is being created by autoref is being "held" across the await point. The following workaround fixes it:

    while let Ok(event) = { let k= &rx; let y = k.recv(); y } {

(albeit with a clippy FP, rust-lang/rust-clippy#8598)

I tried to produce a repro not involving futures etc., using &mut references, but I wasn't able to do so.

ISTM that this is sufficiently strange, and the workaround sufficiently unpleasant, that it was worth a report. I'm not sure if I should be tagging this as a diagnostic issue, or what.

Thanks for your attention.

Meta

rustc --version --verbose:

rustc 1.61.0-nightly (1bfe40d11 2022-03-18)
binary: rustc
commit-hash: 1bfe40d11c3630254504fb73eeccfca28d50df52
commit-date: 2022-03-18
host: x86_64-unknown-linux-gnu
release: 1.61.0-nightly
LLVM version: 14.0.0
@ijackson ijackson added the C-bug Category: This is a bug. label Mar 28, 2022
@Jules-Bertholet
Copy link
Contributor

@rustbot label A-async-await A-lifetimes

@rustbot rustbot added A-async-await Area: Async & Await A-lifetimes Area: Lifetimes / regions labels May 13, 2023
@eholk
Copy link
Contributor

eholk commented May 22, 2023

Out of curiosity, if you build this on nightly and pass -Zdrop-tracking-mir to rustc, does it compile?

This looks like another instance of the issues we've been tracking on #69663.

Basically, what's happening is that the let Some(x) = rx.recv() part in the while loop desugars into a match expression, and match does not create a temporary scope, meaning any temporary values in the thing you're matching on live for the whole while expression. The workaround to make a block with a let binding in it creates a temporary scope, which is why that works.

There's more information at https://doc.rust-lang.org/stable/reference/destructors.html?highlight=scope#temporary-scopes

@eholk eholk added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label May 22, 2023
@ijackson
Copy link
Contributor Author

Out of curiosity, if you build this on nightly and pass -Zdrop-tracking-mir to rustc, does it compile?

Indeed it does. (FTAOD and for my reference, I did RUSTFLAGS=-Zdrop-tracking-mir nailing-cargo +nightly build)

Thanks.

@eholk
Copy link
Contributor

eholk commented May 22, 2023

Great, glad to hear that fixes it! Although, I'm sure a stable solution rather than experimental compiler flag would be better :)

Hopefully we can stabilize -Zdrop-tracking-mir and turn it on by default.

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-lifetimes Area: Lifetimes / regions AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

4 participants