为什么即使使用 .clone(),tokio::spawn 仍然会抱怨生命周期?

5

我正在尝试编译下面看似简单的代码,但是出现了错误:

use std::io::Error;

#[derive(Debug)]
struct NetworkConfig {
    bind: String,
    node_key_file: String,
}

async fn network_handler(network_config: &NetworkConfig) -> Result<(), Error> {
    Ok(())
}

async fn run(network_config: &NetworkConfig) -> Result<(), Error> {
    let network_config_copy = network_config.clone();
    tokio::spawn(async move {
        network_handler(&network_config_copy).await
    }).await?
}

error: cannot infer an appropriate lifetime
  --> src/network.rs:43:18
   |
43 | async fn run(network_config: &NetworkConfig) -> Result<(), Error> {
   |              ^^^^^^^^^^^^^^ ...but this borrow...
44 |     let network_config_copy = network_config.clone();
45 |     tokio::spawn(async move {
   |     ------------ this return type evaluates to the `'static` lifetime...
   |
note: ...can't outlive the lifetime `'_` as defined on the function body at 43:34
  --> src/network.rs:43:34
   |
43 | async fn run(network_config: &NetworkConfig) -> Result<(), Error> {
   |                              ^
help: you can add a constraint to the return type to make it last less than `'static` and match the lifetime `'_` as defined on the function body at 43:34
   |
45 |     tokio::spawn + '_(async move {
   |     ^^^^^^^^^^^^^^^^^

根据以前对这个主题的讨论和例子,我理解将 network_config 的引用传递给生成的闭包会导致生存期问题,因为单独的线程可能会超过 network_config 的生命周期。这就是为什么我正在将 network_config 的克隆移动到生成的线程中,但似乎仍然存在生命周期的不确定性。

是否有任何额外的提示可以给编译器,以便它正确获取生命周期?或者我整个做法都是错误的?


还要注意,按照编译器给出的帮助提示(将tokio::spawn替换为tokio::spawn + '_')实现会导致语法错误。 - dmp32
1个回答

5
如果您想克隆NetworkConfig,您需要实现Clone特质:
#[derive(Debug, Clone)]
struct NetworkConfig {
    bind: String,
    node_key_file: String,
}

否则,按照接收者方法查找规则,您最终将通过以下Clone实现者调用引用上的Clone
impl<'_, T> Clone for &'_ T

克隆出的引用将绑定到clone()调用的作用域期限。

使用derive(Clone)run函数会编译通过,但仅在network_config参数具有'static寿命时才有效,这是由于tokio::spawn生命周期要求所致。

可能这不是您想要的。如果是这种情况,请按值传递NetworkConfig并在调用者上下文中最终进行克隆。

use std::io::Error;

#[derive(Debug, Clone)]
struct NetworkConfig {
    bind: String,
    node_key_file: String,
}

async fn network_handler(network_config: &NetworkConfig) -> Result<(), Error> {
    println!("using {:?}", network_config);
    Ok(())
}

async fn run(network_config: NetworkConfig) -> Result<(), Error> {
    tokio::spawn(async move { network_handler(&network_config).await }).await?
}

#[tokio::main]
async fn main() {
    let config = config::NetworkConfig {
        bind: "my_bind".to_owned(),
        node_key_file: "abc".to_owned(),
    };

    tokio::spawn(run(config.clone()));
}

你可能会问为什么这样可以,实际上仍然将引用传递给了 network_handler()
这是因为 network_config 被移动到了 spawn 异步块内部,从而使得异步块的推断类型获得了静态生命周期。

1
这个答案值得关注:它不仅解决了问题,而且比大多数有关异步/生命周期的教程更深入地洞察了其中的内涵。向你致敬,@attdona! - dmp32

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接