2021年12月博客中的Rust代码为何宣称可编译,实际运行报错?
Rust线程安全计数器编译问题的拆解
为啥你会遇到闭包要move的错误
Rust里用std::thread::spawn创建线程时,线程闭包必须满足'static生命周期约束——说白了就是线程的执行时长不受当前代码块控制,Rust得确保闭包里捕获的内容不会提前失效。如果你的闭包只抓了计数器的&self引用,没把所有权转移给闭包,编译器肯定会报错要求添加move关键字,否则无法保证线程运行时这个引用还处于有效状态。
博客里的代码能编译的两种可能情况
1. 作者展示代码时遗漏了move关键字
正常实现线程安全计数器,肯定要靠Arc<Mutex<Counter>>来实现跨线程的所有权共享,正确写法里闭包必须加move,示例代码如下:
use std::sync::{Arc, Mutex}; use std::thread; struct Counter { count: u32, } impl Counter { fn increment(&self) { let mut count = self.count.lock().unwrap(); *count += 1; } } fn main() { let counter = Arc::new(Mutex::new(Counter { count: 0 })); let mut handles = vec![]; for _ in 0..10 { let counter_clone = Arc::clone(&counter); // 此处必须加move,将counter_clone的所有权转移给线程闭包 let handle = thread::spawn(move || { counter_clone.increment(); }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Count: {}", counter.lock().unwrap().count); }
如果博客里展示的代码没写move,要么是作者笔误遗漏,要么是你查看时没注意到关键细节。
2. 作者使用了Nightly版本的作用域线程
2021年12月时,Rust稳定版还未支持作用域线程(稳定版到2022年8月的1.63版本才正式引入std::thread::scope),但Nightly版本已经可以使用这个不稳定特性。作用域线程能保证线程在指定代码块执行完毕前终止,所以闭包可以直接捕获非'static的引用,不需要move关键字,示例代码如下:
#![feature(scoped_threads)] use std::sync::Mutex; use std::thread; struct Counter { count: Mutex<u32>, } impl Counter { fn increment(&self) { let mut count = self.count.lock().unwrap(); *count += 1; } } fn main() { let counter = Counter { count: Mutex::new(0) }; thread::scope(|s| { for _ in 0..10 { // 此处无需move,作用域保证线程在scope结束前完成执行 s.spawn(|| { counter.increment(); }); } }); println!("Count: {}", counter.count.lock().unwrap()); }
这段代码在2021年的Nightly版本中可以正常编译运行,但依赖不稳定特性,并非稳定版的常规用法。
总结
2021年12月的Rust稳定版中,使用普通thread::spawn的情况下,把increment方法参数从&mut self改为&self的代码必须配合move闭包(或Arc克隆)才能编译。博客里的代码能正常运行,要么是作者使用了Nightly版的作用域线程,要么是展示代码时遗漏了move关键字。
内容的提问来源于stack exchange,提问作者Zebrafish
相关产品推荐
相关产品推荐

