Incorrect judgement of ownership moving

This demo doesn't compile because rustc said that async block is not Send.

#![allow(unused)]

use std::{sync::Mutex, future::Future};

fn main() {
    require_send(async {
        let mutex = Mutex::new(());
        let guard = mutex.lock().unwrap();
        drop(guard);
        std::future::pending::<()>().await;
    });
}

fn require_send(_: impl Future + Send) {}
error: future cannot be sent between threads safely
  --> src/main.rs:6:18
   |
6  |       require_send(async {
   |  __________________^
7  | |         let mutex = Mutex::new(());
8  | |         let guard = mutex.lock().unwrap();
9  | |         drop(guard);
10 | |         std::future::pending::<()>().await;
11 | |     });
   | |_____^ future created by async block is not `Send`
   |
   = help: within `[async block@src/main.rs:6:18: 11:6]`, the trait `Send` is not implemented for `MutexGuard<'_, ()>`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:10:37
   |
8  |         let guard = mutex.lock().unwrap();
   |             ----- has type `MutexGuard<'_, ()>` which is not `Send`
9  |         drop(guard);
10 |         std::future::pending::<()>().await;
   |                                     ^^^^^^ await occurs here, with `guard` maybe used later
11 |     });
   |     - `guard` is later dropped here
note: required by a bound in `require_send`
  --> src/main.rs:14:34
   |
14 | fn require_send(_: impl Future + Send) {}
   |                                  ^^^^ required by this bound in `require_send`

But why? MutexGuard will be dropped at drop(guard);, not at the end of the async block. guard won't be used later.

This async block will be considered implements Send:

async {
    let mutex = Mutex::new(());
    {
        let guard = mutex.lock().unwrap();
    }
    std::future::pending::<()>().await;
}

What's the difference between them? Thanks.

This is a known limitation of rustc's current analysis of async blocks/fns. As far as I'm aware, it will not consider static analysis of variable initialization and de-initialization to determine the types of values held over .await boundaries, instead just working with the lexical scope of variables, thus incorrectly concluding that your code might be holding a (non-Send) mutex guard over await. So while functionally, limiting the life of guard with a drop or a block behaves the same, only one is properly supported by the current analysis rustc does for async in this context.

2 Likes

Thanks a lot

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.