How do I borrow a mutable reference from an Arc<Mutex<T>> and call an async member function on it?

Viewed 282

I have a simple struct and an implementation that looks like this.

#[derive(Debug)]
struct MyStruct {
    data: u64,
}

impl MyStruct {
    async fn something_async(&mut self) -> Result<(), Box<dyn Error>> {
        self.data += 1;
        Ok(())
    }
}

I want to use MyStruct on the heap using a smart pointer and a mutex so that I can use it from multiple threads.

However, when I try to call the async function called something_async()...

tokio::spawn(async move {
    let ptr = Arc::new(Mutex::new(MyStruct { data: 1 }));
    let mut s = ptr.lock().unwrap();
    s.something_async().await.unwrap();
    println!("{:?}", s);
});

...I get the following error:

error: future cannot be sent between threads safely
   --> src/main.rs:18:5
    |
18  |     tokio::spawn(async move {
    |     ^^^^^^^^^^^^ future created by async block is not `Send`
    |
    = help: within `impl Future<Output = [async output]>`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, MyStruct>`
note: future is not `Send` as this value is used across an await
   --> src/main.rs:21:9
    |
20  |         let mut s = ptr.lock().unwrap();
    |             ----- has type `std::sync::MutexGuard<'_, MyStruct>` which is not `Send`
21  |         s.something_async().await.unwrap();
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `mut s` maybe used later
22  |         println!("{:?}", s);
23  |     });
    |     - `mut s` is later dropped here
note: required by a bound in `tokio::spawn`
   --> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.16.1/src/task/spawn.rs:127:21
    |
127 |         T: Future + Send + 'static,
    |                     ^^^^ required by this bound in `tokio::spawn`

I am assuming that the compiler does not like me keeping a MutexGuard held across an await, so I tried this instead:

let fut = {
    let mut s = ptr.lock().unwrap();
    s.something_async()
};
fut.await.unwrap();

But of course then it complains about the Future outliving the mutable reference not living long enough:

error[E0597]: `s` does not live long enough
  --> src/main.rs:22:13
   |
20 |         let fut = {
   |             --- borrow later stored here
21 |             let mut s = ptr.lock().unwrap();
22 |             s.something_async()
   |             ^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
23 |         };
   |         - `s` dropped here while still borrowed

How do I call an async method on an object wrapped in an Arc<Mutex<T>>?

1 Answers

Make MyStruct copyable.

#[derive(Debug, Copy, Clone)]
struct MyStruct {
  data: u64,
}

impl MyStruct {
    async fn something_async(&mut self) -> Result<(), Box<dyn Error>> {
        self.data += 1;
        Ok(())
    }
}

#[tokio::main]
async fn main() {
    tokio::spawn(async move {
        let ptr = Arc::new(Mutex::new(MyStruct { data: 1 }));
        let mut s = *ptr.lock().unwrap();
        s.something_async().await.unwrap();
        println!("{:?}", s);
    });
}
Related