user2228392
user2228392

Reputation: 404

How to fix "data is required to be 'static" using tokio::spawn?

I'm trying to listen to multiple TCP streams via tokio but I get an error:

use rybw_config::ListenerConfig;
use tokio::prelude::*;
use tokio::task::JoinHandle;
pub mod tcp;
use tcp::TCPListener;

pub struct Listener {
    config: ListenerConfig,
    tcp: Vec<tcp::TCPListener>,
}

impl Listener {
    pub fn new(config: ListenerConfig) -> Listener {
        let tcp: Vec<TCPListener> = config
            .tcp
            .clone()
            .unwrap()
            .iter()
            .map(|x| TCPListener::new((*x).clone()))
            .collect();

        Listener {
            tcp: tcp,
            config: config.clone(),
        }
    }

    pub async fn run(&self) {
        let handles: Vec<JoinHandle<()>> = self.tcp.iter().map(|i| {
            tokio::spawn(async {
                i.run().await
            })
        }).collect();
        futures::future::join_all(handles);
    }
error: cannot infer an appropriate lifetime
  --> rybw-listener/src/lib.rs:28:22
   |
28 |     pub async fn run(&self) {
   |                      ^^^^^
   |                      |
   |                      data with this lifetime...
   |                      ...is captured here...
29 |         let handles: Vec<JoinHandle<()>> = self.tcp.iter().map(|i| {
30 |             tokio::spawn(async {
   |             ------------ ...and required to be `'static` by this

Upvotes: 2

Views: 1820

Answers (2)

kmdreko
kmdreko

Reputation: 60347

The type of i in your example is &tcp::TCPListener where the reference is still tied to self. However, tokio::spawn requires the spawned task to be 'static meaning it can't keep references to local variables.

The solution is to move owned values into the task. Common ways to do this are:

  • Use Clone to create a copy of the data needed by the task.

    let handles: Vec<_> = self.tcp.iter().map(|listener| {
        let listener = listener.clone();
                            // ^^^^^^^^ create a copy
        tokio::spawn(async move {
                        // ^^^^ and move it into the async block
            listener.run().await
        })
    }).collect();
    
  • Use shared ownership via Arc and Clone the handle needed by the task.

    use std::sync::Arc;
    
    struct Listener {
        config: ListenerConfig,
        tcp: Vec<Arc<tcp::TCPListener>>,
              // ^^^ allow shared ownership
    }
    
    let handles: Vec<_> = self.tcp.iter().map(|listener| {
        let listener = Arc::clone(listener);
                    // ^^^^^^^^^^ create a new handle to the same data
        tokio::spawn(async move {
                        // ^^^^ and move it into the async block
            listener.run().await
        })
    }).collect();
    
  • Provide ownership directly to the task. This won't really be an option in your example since &self is an immutable reference. But if you had an owned self or mutable reference and didn't need to keep your listeners after spawning tasks for them, you could use something like .into_iter() or .drain() on Vec to consume the listeners so their type is tcp::TCPListener (not a reference) and can easily be move-d into the task.

Upvotes: 0

user2228392
user2228392

Reputation: 404

Solved by myself.

rust closure captures the struct, not by individual fields. So we wrapper the TCPListener with std::sync::Arc and cloning it then used in spawn async {}

Upvotes: 3

Related Questions