Rust中for循环与for_each方法的细微差异
嗨,这个问题我当初刚学Rust的时候也踩过一模一样的坑!其实核心在于两种遍历方式拿到的元素所有权状态完全不同,咱们一步步拆解来看:
先看能正常运行的for循环
当你写 for t in all_threads 时,这里的all_threads是一个Vec,它会自动转换成IntoIter类型的迭代器——这个迭代器的作用就是把Vec里的元素逐个转移所有权给循环变量t。也就是说,每次循环里的t都是一个完整的JoinHandle实例,你完全拥有它的所有权,自然可以调用t.join()(因为JoinHandle::join()方法需要消耗这个实例,也就是把它move进去)。等循环结束后,原Vec里的元素就都被移走了,Vec本身也会被自动丢弃。
再看不work的for_each写法
而all_threads.iter_mut()创建的是一个可变引用迭代器(IterMut类型),它只会给你每个元素的&mut JoinHandle(可变借用),而不是所有权。当你在闭包里调用t.join()时,join()需要的是self(也就是完整的所有权),但你手里只有一个可变引用——Rust的所有权规则绝对不允许你从引用背后把值直接移出来,这就触发了你看到的错误:cannot move out of *t which is behind a shared reference。
为什么你会觉得是语法糖?
其实for循环和for_each的底层逻辑并不完全等价:for循环会根据你遍历的对象自动选择迭代器类型——如果遍历的是Vec本身,就是消耗型的IntoIter;如果是&all_threads就是不可变借用迭代器,&mut all_threads就是可变借用迭代器。而iter_mut()是明确指定要创建可变借用迭代器,所以for_each拿到的只能是引用,没法直接调用需要所有权的方法。
如何让第二个例子正常运行?
只需要把iter_mut()换成into_iter(),这样就会创建消耗型迭代器,闭包里拿到的就是拥有所有权的JoinHandle了:
fn main() { let all_threads: Vec<std::thread::JoinHandle<()>> = vec![]; all_threads.into_iter().for_each(move |t| t.join().unwrap()); }
总结一下:关键是搞清楚**消耗型迭代器(IntoIter)和借用型迭代器(Iter/IterMut)**的区别,这就是两种写法行为差异的根源啦!
备注:内容来源于stack exchange,提问作者Paperino

