Skip to content

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

Closed
@ijackson

Description

@ijackson
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

Activity

Jules-Bertholet

Jules-Bertholet commented on May 13, 2023

@Jules-Bertholet
Contributor

@rustbot label A-async-await A-lifetimes

eholk

eholk commented on May 22, 2023

@eholk
Contributor

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

added
AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
on May 22, 2023
ijackson

ijackson commented on May 22, 2023

@ijackson
ContributorAuthor

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

eholk commented on May 22, 2023

@eholk
Contributor

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.

compiler-errors

compiler-errors commented on Dec 31, 2024

@compiler-errors
Member

This works now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitA-lifetimesArea: Lifetimes / regionsAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @eholk@ijackson@compiler-errors@rustbot@Jules-Bertholet

        Issue actions

          Confusing extension of lifetime of reference causes lack of Send in Future · Issue #95412 · rust-lang/rust