Reputation: 404
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
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
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