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

Rust线程循环中如何传递变量并更新结构体?

Rust线程循环中更新结构体的变量传递问题

需要编写一段Rust代码,每隔几秒发起一次网络请求并更新一个结构体。尝试用线程实现时,在变量移入线程、将请求结果赋值给结构体的过程中遇到困难。以下是不含网络请求的示例代码,用于复现变量传递问题:

use std::thread;

#[derive(Debug, Clone)]
struct input_holder {
    i : String,
}

fn main() {
    // pass this string into the thread loop.
    let input = String::from("This is a string");
    let input_loop = input.clone();
    
    //assign the output of the method to this struct.
    let ih = input_holder {
        i : input
    };

    thread::spawn(|| {
        let runtime = tokio::runtime::Runtime::new().unwrap();
        loop {
            let _ = runtime.block_on(runtime.spawn(async move {
                let _new_input = match update_string(input_loop.clone(), ih.clone()){
                    Some(ni) => ni,
                    None => String::from("None")
                };
            }));
        }
    });
}

//This is where I would do the web request. I can test this method outside of the thread and it works as expected.
pub fn update_string(_input: String, mut ih: input_holder) -> Option<String> {
    ih.i = String::from("This is an updated string");
    Some(String::from("This is an updated string"))
}

运行代码时遇到如下错误:

error[E0382]: use of moved value: `ih`
  --> src/main.rs:64:63
   |
64 |               let _ = runtime.block_on(runtime.spawn(async move {
   |  _______________________________________________________________^
65 | |                 let _new_input = match update_string(input_loop.clone(), ih.clone()){
   | |                                                                          -- use occurs due to use in generator
66 | |                     Some(ni) => ni,
67 | |                     None => String::from("None")
68 | |                 };
69 | |             }));
   | |_____________^ value moved here, in previous iteration of loop
   |
   = note: move occurs because `ih` has type `input_holder`, which does not implement the `Copy` trait

用户不清楚如何传递ih,不理解use occurs due to use in generator的含义,尝试过克隆、借用、移除loop中的move(无法为结构体实现Copy trait)等方法均未解决,想知道线程循环中正确传递变量的方式,以及如何避免不必要的复制。


错误原因分析

  1. async move的变量捕获问题:async move闭包会强制捕获变量的所有权。第一次循环时,ih被移动到异步任务中,第二次循环时ih已经失去所有权,因此报错“use of moved value”。
  2. 副本无法更新原结构体:就算使用ih.clone(),修改的只是结构体的副本,主线程中的原ih不会受到任何影响,不符合“更新结构体”的需求。
  3. use occurs due to use in generator的含义:异步闭包本质是一个生成器(generator),它会捕获变量并在未来执行,而move关键字导致变量所有权被转移到生成器内部,后续循环无法再访问该变量。

正确解决方案:使用Arc<Mutex>共享可变状态

要在多线程中安全地共享并修改结构体,需要使用Arc(原子引用计数,用于跨线程共享所有权)和Mutex(互斥锁,保证同一时间只有一个线程能修改数据)的组合。

修改后的代码示例

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[derive(Debug, Clone)]
struct InputHolder {
    i: String,
}

fn main() {
    let input = String::from("This is a string");
    // 用Arc<Mutex>包装结构体,实现跨线程共享可变访问
    let ih = Arc::new(Mutex::new(InputHolder { i: input }));
    // 克隆Arc,传递给线程(Arc的克隆是轻量的,只是增加引用计数)
    let ih_thread = Arc::clone(&ih);

    thread::spawn(move || {
        let runtime = tokio::runtime::Runtime::new().unwrap();
        loop {
            // 克隆Arc到异步任务中
            let ih_task = Arc::clone(&ih_thread);
            runtime.block_on(async move {
                // 锁定Mutex,获取可变引用
                let mut ih_guard = ih_task.lock().unwrap();
                // 调用更新方法,直接修改原结构体的内容
                if let Some(new_str) = update_string("".to_string(), &mut ih_guard) {
                    ih_guard.i = new_str;
                    println!("Updated struct: {:?}", ih_guard);
                }
            });
            // 每隔3秒执行一次
            thread::sleep(Duration::from_secs(3));
        }
    });

    // 保持主线程运行,避免程序退出
    thread::sleep(Duration::from_secs(30));
}

// 修改方法,接收结构体的可变引用,直接修改原数据
pub fn update_string(_input: String, ih: &mut InputHolder) -> Option<String> {
    let new_str = String::from("This is an updated string");
    // 直接修改传入的结构体引用
    ih.i = new_str.clone();
    Some(new_str)
}

关键说明

  • Arc<Mutex<InputHolder>>:Arc让多个线程可以共享同一个结构体的所有权,Mutex保证每次只有一个线程能修改结构体,避免数据竞争。
  • 克隆Arc而非结构体:Arc的克隆操作非常轻量,只是增加引用计数,不会复制整个结构体,避免了不必要的内存开销。
  • 锁定Mutex修改数据:通过lock()方法获取互斥锁的守卫(guard),守卫会自动在作用域结束时释放锁,保证线程安全。
  • 移除不必要的async move所有权转移:通过克隆Arc到异步任务中,避免了原变量被移动的问题,同时能安全地访问共享结构体。

额外优化:直接使用Tokio的异步任务

如果项目本身基于Tokio,可以不用手动创建线程,直接用Tokio的spawn和timer来实现定时任务,代码会更简洁:

use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};

#[derive(Debug, Clone)]
struct InputHolder {
    i: String,
}

#[tokio::main]
async fn main() {
    let input = String::from("This is a string");
    let ih = Arc::new(Mutex::new(InputHolder { i: input }));

    tokio::spawn(async move {
        loop {
            let ih_task = Arc::clone(&ih);
            let mut ih_guard = ih_task.lock().unwrap();
            if let Some(new_str) = update_string("".to_string(), &mut ih_guard) {
                ih_guard.i = new_str;
                println!("Updated struct: {:?}", ih_guard);
            }
            sleep(Duration::from_secs(3)).await;
        }
    });

    // 保持主线程运行
    sleep(Duration::from_secs(30)).await;
}

pub fn update_string(_input: String, ih: &mut InputHolder) -> Option<String> {
    let new_str = String::from("This is an updated string");
    ih.i = new_str.clone();
    Some(new_str)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:05:18