Skip to content

Explain why borrows can't be held across yield point in async blocks/functions #78938

Closed
@jyn514

Description

@jyn514

Consider the following code:

use std::sync::Arc;
use tokio::runtime::Runtime; // 0.3.1

async fn f() {
    let room_ref = Arc::new(Vec::new());

    let gameloop_handle = Runtime::new().unwrap().spawn(async {
        game_loop(Arc::clone(&room_ref))
    });
    gameloop_handle.await;
}

fn game_loop(v: Arc<Vec<usize>>) {}

The error message has a helpful hint:

help: to force the async block to take ownership of `room_ref` (and any other referenced variables), use the `move` keyword
  |
7 |     let gameloop_handle = Runtime::new().unwrap().spawn(async move {
8 |         game_loop(Arc::clone(&room_ref))
9 |     });

But it doesn't explain very well why move is necessary:

error[E0373]: async block may outlive the current function, but it borrows `room_ref`, which is owned by the current function
 --> src/lib.rs:7:63
  |
7 |       let gameloop_handle = Runtime::new().unwrap().spawn(async {
  |  _______________________________________________________________^
8 | |         game_loop(Arc::clone(&room_ref))
  | |                               -------- `room_ref` is borrowed here
9 | |     });
  | |_____^ may outlive borrowed value `room_ref`
  |
note: function requires argument type to outlive `'static`
 --> src/lib.rs:7:57
  |
7 |       let gameloop_handle = Runtime::new().unwrap().spawn(async {
  |  _________________________________________________________^
8 | |         game_loop(Arc::clone(&room_ref))
9 | |     });
  | |_____^

In particular, 'async block may outlive the current function' makes no sense without a very good mental model of async: the future is awaited within the current function, so of course it can't outlive it. The thing to realize here is that even though it's written as one function, it's actually two: one executed before the yield point, and one after, and the stack space for the first function goes away when you call await.

It would be nice to instead say something like

note: borrows cannot be held across a yield point, because the stack space of the current function is not preserved
help: see https://rust-lang.github.io/async-book/03_async_await/01_chapter.html#awaiting-on-a-multithreaded-executor for more information

Metadata

Metadata

Labels

A-async-awaitArea: Async & AwaitA-diagnosticsArea: Messages for errors, warnings, and lintsA-lifetimesArea: Lifetimes / regionsAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.D-confusingDiagnostics: Confusing error or lint that should be reworked.D-newcomer-roadblockDiagnostics: Confusing error or lint; hard to understand for new users.E-easyCall for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.E-mentorCall for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions