Lifetime problems with tokio::spawn() argument requires that `'a` must outlive `'static`

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.)

3 Likes

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.

1 Like

You don't need spawn. You can run multiple futures by awaiting them together:

let handle = async {
       while rx.recv().await.is_ok() {
           user.greet();
       } 
};

let other_stuff = async {
    tx.send().await;
};

futures::future::join_all!(handle, other_stuff);

Also consider using streams. futures::stream::from_iter + buffer_unordered + collect.

3 Likes

Or choose a spawn that doesn't require 'static (not recommended though): Why you might actually want async in your project รขย€ย“ notgull รขย€ย“ The world's \#1 source of notgull

1 Like

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.

Yes.


Why don't you just make User own the name string?

1 Like

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.

3 Likes

Why don't you just make User own the name string?

Yeah, I'm going to own the name string. Just wanted to bring up this problem to hear your advice so I can understand Rust better. Thanks.

Thanks for your explanation. It helped me a lot.

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.

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.