Send trait and async/await

Hello!

I'm trying to understand what the compiler is telling me. I have the following program:

use std::cell::RefCell;
use std::collections::HashMap;
use std::future::Future;

#[derive(Default)]
struct IntMut(RefCell<HashMap<u32, u32>>);

impl IntMut {
    async fn async_test(&self) { }
}

async fn my_async() {
    let im = IntMut::default();
    im.async_test().await;
}

fn do_stuff<T: Future + Send + 'static>(stuff: T) { }

fn main() {
    do_stuff(my_async())
}

which gives me:

17 | fn do_stuff<T: Future + Send + 'static>(stuff: T) { }
   |    --------             ---- required by this bound in `do_stuff`
...
20 |     do_stuff(my_async())
   |     ^^^^^^^^ `std::cell::RefCell<std::collections::HashMap<u32, u32>>` cannot be shared between threads safely
   |
   = help: within `IntMut`, the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<std::collections::HashMap<u32, u32>>`
   = note: required because it appears within the type `IntMut`
   = note: required because of the requirements on the impl of `std::marker::Send` for `&IntMut`
   = note: required because it appears within the type `for<'r, 's, 't0> {IntMut, &'r IntMut, IntMut, impl std::future::Future, impl std::future::Future, ()}`
   = note: required because it appears within the type `[static generator@src/main.rs:12:21: 15:2 for<'r, 's, 't0> {IntMut, &'r IntMut, IntMut, impl std::future::Future, impl std::future::Future, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:12:21: 15:2 for<'r, 's, 't0> {IntMut, &'r IntMut, IntMut, impl std::future::Future, impl std::future::Future, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `impl std::future::Future`

By itself, IntMut is Send. If I do not include the call to im.async_test().await in my_async then the future returned by my_async is indeed Send and the compiler is happy.

It's only when I do the await that I get this error. My reading of the error is that the generator created by the await call ends up storing a reference to IntMut, and &IntMut is not Send, so therefore the future is not Send.

Is that right?

Two follow up questions:

  • Is this a real problem or is this a case of the compiler being too conservative? I didn't create an &IntMut explicitly and I don't have access to it, so I don't see how it could be shared by multiple threads simultaneously?
  • Is there any workaround other than, say, using Mutex instead of RefCell, despite the fact that this struct will never be access simultaneously by multiple threads?

This works but you will get problem as soon as you introduce self to the inner async.

impl IntMut {
    fn async_test(&self) -> impl Future<Output=()> + Send + '_ { async {} }
}

async fn my_async() {
    let im = IntMut::default();
    let f = im.async_test();
    f.await;
}

Your function call desugars to:

async fn my_async() {
    let im = IntMut::default();
    IntMut::async_test(&im).await;
}

So the future returned by async_test contains an &IntMut. Using RefCell like this is rather difficult. One option would be to make IntMut have no async functions, and do allow .borrow() calls inside non-async methods on IntMut.

Thanks for the response!

I think I'm still missing something. I get the de-sugaring and with that the future contains an &IntMut, but if that was all that was going on, wouldn't any function call on im cause the function call to no longer be Send because it'd contain an &IntMut? But if I call a non-async function on IntMut the compiler is happy, e.g.:

impl IntMut {
    async fn async_test(&self) { }
    fn non_async(&self) { }
}

async fn my_async() {
    let im = IntMut::default();
    //im.async_test().await;
    im.non_async();
}

This example compiles, even though I assume that'd desurgar to IntMut::non_async(&im)?

The difference is that your non-async call does not keep an &IntMut across an .await point.

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.