Manual drop of !Send value doesn't make async block sendable

Context:
I need to use Mutexes (or even better: RefCells if that was possible) in some async closures, blocks, or functions. I would like the resulting Futures to be Send. The locking (or borrowing, respectively) will only be necessary for a short time while no async operations are executed.

Problem:
If I use a block expression to limit the scope of the MutexGuard (or mutable borrow, respectively), I can make the Future (named "_sendable_future") be Send. But it won't work with the "_nonsend_future" below. The only difference is that in the latter case, I use a manual drop instead of making an extra block expression and letting the guard (or borrow, respectively) drop because their drop scope ends.

Question:
Why do these two cases behave differently? Is that because the compiler doesn't "notice" that the values are dropped when constructing the Future from the async block?

Thanks for your help, I include a playground example below:

use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::sync::Mutex;

async fn some_func() {}

fn main() {
    // This works fine:
    let cell1 = RefCell::new(0);
    let mutex1 = Mutex::new(0);
    let _sendable_future: Pin<Box<dyn Future<Output = ()> + Send>> =
        Box::pin(async move {
            {
                let mut borrow = cell1.borrow_mut();
                let mut guard = mutex1.lock().unwrap();
                *borrow += 1;
                *guard += 1;
            }
            some_func().await;
        });

    // But this doesn't work:
    let cell2 = RefCell::new(0);
    let mutex2 = Mutex::new(0);
    // (unless we remove the `Send` bound)
    let _nonsend_future: Pin<Box<dyn Future<Output = ()> + Send>> =
    //let _nonsend_future: Pin<Box<dyn Future<Output = ()>>> =
        Box::pin(async move {
            let mut borrow = cell2.borrow_mut();
            let mut guard = mutex2.lock().unwrap();
            *borrow += 1;
            *guard += 1;
            drop(borrow);
            drop(guard);
            some_func().await;
        });
}

(Playground)

(Edit: Formatting only)

This looks like an instance of Tracking issue for more precise generator captures · Issue #69663 · rust-lang/rust · GitHub

Looks like this is a consequence of how the compiler decides which types are captured in the future, and that's by looking at the locals in scope at await points, ignoring whether they can be alive or not. This is a bit tricky to fix because the future's type is computed in an earlier stage than the one where liveness is known.

3 Likes

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.