You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何使用async move仍无法转移两个struct的所有权?

Rust异步任务调度中的E0507错误修复

错误信息

cargo build
   Compiling playground v0.1.0 (/home/explorer/playground)
error[E0507]: cannot move out of `config`, a captured variable in an `FnMut` closure
  --> src/main.rs:50:26
   |
36 |   pub fn execute_jobs(config: Arc<Config>) {
   |                       ------ captured outer variable
...
49 |               .with_run_async(Box::new(|_, _| {
   |                                        ------ captured by this `FnMut` closure
50 |                   Box::pin(async move {
   |  __________________________^
51 | |                     println!("run async every 2 seconds");
52 | |
53 | |                     do_job(&config.first, settings).await.unwrap();
   | |                             ------
   | |                             |
   | |                             variable moved due to use in coroutine
   | |                             move occurs because `config` has type `Arc<Config>`, which does not implement the `Copy` trait
54 | |                 })
   | |_________________^ `config` is moved here
For more information about this error, try `rustc --explain E0507`.

settings也会触发同类错误。

问题原因

核心矛盾在于:

  • with_run_async接收的是FnMut类型闭包,这类闭包会被调度器多次调用(每次Cron触发时执行一次)。
  • 在async move块中直接使用config和settings,会把这两个变量的所有权移动到异步块里。但变量只能被移动一次,第一次调用闭包后,原变量就被移走了,后续调用时无变量可移,编译器因此报错。

修复方案

1. 处理config(Arc类型)

Arc是原子引用计数类型,支持低成本克隆。只需在FnMut闭包内部克隆一份Arc,每次调用闭包都会生成新的引用,不会移动原变量所有权:

.with_run_async(Box::new(|_, _| {
    let config = config.clone(); // 克隆Arc获取新引用
    Box::pin(async move {
        // ... 后续使用config
    })
}))

2. 处理settings

给Settings实现Clone trait,这样每次调用闭包时可以克隆一份实例:

#[derive(Clone)]
struct Settings {
    first: String,
    second: String,
}

然后在闭包内克隆settings:

.with_run_async(Box::new(|_, _| {
    let config = config.clone();
    let settings = settings.clone(); // 克隆Settings实例
    Box::pin(async move {
        // ... 后续使用settings
    })
}))

完整修复代码

use std::{sync::Arc, time::Duration};
use tokio_cron_scheduler::{JobBuilder, JobScheduler};

struct Config {
    first: String,
    second: String,
}

#[derive(Clone)]
struct Settings {
    first: String,
    second: String,
}

#[tokio::main]
async fn main() {
    let config = Config {
        first: "first".to_string(),
        second: "second".to_string(),
    };

    execute_jobs(Arc::new(config));

    // 模拟后台服务运行
    tokio::time::sleep(Duration::from_secs(120)).await;
}

pub fn execute_jobs(config: Arc<Config>) {
    tokio::task::spawn(async move {
        let settings = Settings {
            first: "first".to_string(),
            second: "second".to_string(),
        };

        let scheduler = JobScheduler::new().await.unwrap();

        let job = JobBuilder::new()
            .with_cron_job_type()
            .with_schedule("*/2 * * * * *")
            .unwrap()
            .with_run_async(Box::new(|_, _| {
                let config = config.clone();
                let settings = settings.clone();
                Box::pin(async move {
                    println!("每2秒执行一次异步任务");
                    do_job(&config.first, settings).await.unwrap();
                })
            }))
            .build()
            .unwrap();

        scheduler.add(job).await.unwrap();

        scheduler.start().await.unwrap();
    });
}

async fn do_job(first_config: &str, settings: Settings) -> Result<(), ()> {
    dbg!(first_config);
    dbg!(settings.second);
    Ok(())
}

内容的提问来源于stack exchange,提问作者Fred Hors

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 10:02:45