This code doesn't compile:
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn t() {
tokio::spawn(async {
let m = std::sync::Mutex::new("hi".to_string());
let guard = m.lock().unwrap();
let value = guard.clone();
drop(guard);
tokio::time::sleep(std::time::Duration::from_micros(1)).await;
println!("{value}");
});
}
with this error message:
error: future cannot be sent between threads safely
--> src/net/dns.rs:1132:9
|
1132 | / tokio::spawn(async {
1133 | | let m = std::sync::Mutex::new("hi".to_string());
1134 | | let guard = m.lock().unwrap();
1135 | | let value = guard.clone();
... |
1140 | | println!("{value}");
1141 | | });
| |__________^ future created by async block is not `Send`
|
= help: within `{async block@src/net/dns.rs:1132:22: 1132:27}`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, std::string::String>`, which is required by `{async block@src/net/dns.rs:1132:22: 1132:27}: std::marker::Send`
note: future is not `Send` as this value is used across an await
--> src/net/dns.rs:1138:69
|
1134 | let guard = m.lock().unwrap();
| ----- has type `std::sync::MutexGuard<'_, std::string::String>` which is not `Send`
...
1138 | tokio::time::sleep(std::time::Duration::from_micros(1)).await;
| ^^^^^ await occurs here, with `guard` maybe used later
note: required by a bound in `tokio::spawn`
--> /home/jonathan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.38.1/src/task/spawn.rs:166:21
|
164 | pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
| ----- required by a bound in this function
165 | where
166 | F: Future + Send + 'static,
| ^^^^ required by this bound in `spawn`
This really surprises me, and there seems to be an incorrectness in my mental model of rust async. We are dropping guard
, so why is it still alive at the .await
?
I know that I can rewrite the code as
let value = m.lock().unwrap().clone();
or
let value = {
let guard = m.lock().unwrap();
guard.clone()
};
and it will compile, but this is not my question. My question is: Why can't I hold the MutexGuard across the await point even though it's dropped? Is there some bigger misunderstanding in what I thought drop(guard)
does, or is this just a peculiarity of how async state machines are built?