I have a lifetime problem with the tokio::spawn() method. The code is shown below, and the playground link is Rust Playground
The code won't compile and compiles argument requires that 'a must outlive 'static.
I know that I can get around this problem by replacing name: &'a str with name: String, but I'm wondering if there are other methods to fix this. I also turned to use Cow<'a, str>, but it didn't work either. Not sure if my usage of Cow is correct since I'm new to Cow.
use tokio; // 1.32.0
#[derive(Debug)]
struct User<'a> {
name: &'a str,
}
impl<'a> User<'a> {
fn new(name: &'a str) -> Self {
Self {
name,
}
}
fn greet(&self) {
println!("Hi, my name is {}", self.name);
}
}
async fn long_running_task(user: User<'_>) {
println!("{:?}", user);
// Start a background task that receive events and perform actions.
let (_tx, rx) = std::sync::mpsc::channel::<()>();
let handle = tokio::spawn(async move {
while rx.recv().is_ok() {
user.greet();
}
});
// Long running task started in the main thread
// For example, the event loop starts to run here.
// It will call tx.send(()) to notify some events
// ... ...
// ... ...
// Long running task finshed
// Cancel the background task
handle.abort();
}
#[tokio::main]
async fn main() {
let name = String::from("Joe"); // in realworld, name comes from the user input
let user = User::new(&name);
long_running_task(user).await;
}
The spawn() function itself specifies that it needs the 'static bound, because that's required for soundness. You can't get around this in any way โ you must not try and capture any short-living references. (Read the documentation of std::thread::spawn() for the rationale.)
Cow is usually not the right choice. It is great for things like "replace the non-unicode in this string if you have to display it" but as you can see it still has a lifetime parameter and so it's not good for long-lasting values. If you don't want to use String you might be able to use Arc<str> instead.
The solution can be compiled. However, it cannot run because the first async block is an infinite loop, so the second handle won't get the chance to run. Is that possible to get around this? Thanks.
I forgot to add .await to the code โ you're not supposed to use blocking channels in async, even if you spawn. Also note that join_handle.abort() can't interrupt blocking code. It only affects .await points. If you abort a future with an infinite loop without .await, the loop will continue running.
Tokio has proper async-compatible channels. With async channels, the first future would yield on recv().await, and let the other future run. Similarly send().await would be pending and let the first future run again. When the sending future completes, it will drop, including its sender, and that will let the first one stop receiving and finish.
Sorry, I didn't mean to sound rude or anything. I had not seen that you had already mentioned that option in your original post and that you were exploring alternative options.