Rust中不同for循环变体的写法差异与报错原因咨询
要搞懂这些写法差异,先记住两个核心规则:
for <模式> in <可迭代对象>语法中,<模式>部分是解构匹配规则,不是变量类型声明。循环会把迭代器逐次产出的值,按你写的模式解构后绑定到对应变量上。for会自动对传入的可迭代对象调用into_iter(),不同类型调用后产出的迭代器元素类型不同,所有权规则也不同:- 传入不可变引用
&Vec<T>:迭代元素类型为&T,不可变引用实现了Copy特性,迭代时不会拿走原变量的所有权 - 传入可变引用
&mut Vec<T>:迭代元素类型为&mut T,可变引用不实现Copy特性,直接传参会拿走原可变引用的所有权,迭代结束后原变量失效 - 传入按值的
Vec<T>:迭代元素类型为T,会逐个拿走容器内元素的所有权
- 传入不可变引用
分点解答你的疑问
1. loop1和loop2的写法差异
两者的唯一区别是循环头写的匹配模式不同:
loop2中写for i in list:list是&Vec<i32>,迭代产出的元素是&i32。这里i是无解构的变量绑定模式,直接把&i32绑定给i,所以i的类型就是&i32,必须用*i解引用才能拿到内部的i32值。如果直接传i给f64::from,就会报f64未实现From<&i32>的错误。loop1中写for &i in list:这里的&i是引用解构模式,不是声明i为引用类型。迭代产出的&i32会被这个模式匹配,自动把引用指向的内部值绑定给i,因此i的类型直接是i32,不需要也不能再解引用——写*i会报错,因为i32是基础数值类型,不是指针,无法被解引用。
这个解构逻辑和let语句完全一致:写
let &x = &10;时,x的类型就是i32,值为10。
你提到的loop3/4、loop5/6的差异和这个逻辑完全相同:loop4、loop7中i直接绑定迭代产出的引用,需要手动解引用;loop5、loop6用&i/&mut i的解构模式,直接拿到内部的i32值,无需解引用。
2. loop3_bad编译错误的根本原因
这个错误踩的是可变引用的所有权规则:loop3_bad的参数是&mut Vec<i32>类型的可变引用,可变引用不支持Copy。你直接写for xxx in list时,会隐式调用list.into_iter(),这个调用会拿走list这个可变引用本身的所有权,循环结束后list变量已经失效,后续再调用list.len()就会触发“借用已移动的值”的错误。
为什么loop1、loop2没有这个问题?因为这两个函数的参数是不可变引用&Vec<i32>,不可变引用实现了Copy特性,调用into_iter()时会自动拷贝一份新的引用用于迭代,原list变量始终有效,循环结束后可以正常调用方法。
编译器给出的修复提示是正确的:只要不直接把list传给for循环,而是传入&list或者&mut list,就不会拿走原引用的所有权,避免这个错误。
3. &*list和&mut *list的作用
这是手动创建新引用、避免拿走原可变引用所有权的写法:list本身是&mut Vec<i32>类型,先写*list解引用得到Vec<i32>类型的原列表,再写&/&mut取这个列表的引用,就得到了一个全新的引用。把这个新引用传给for循环迭代,不管迭代过程中怎么转移所有权,都不会影响原来的list变量,循环结束后可以正常调用list.len()。
实际开发中不需要写这么绕的语法,直接写for xxx in &list或者for xxx in &mut list就能达到完全一样的效果,可读性更好。
常用写法速查表
以后写循环可以直接对照这个表,不会再出类型错误:
| 函数参数类型 | 循环写法 | 循环变量i的类型 | 变量使用方式 |
|---|---|---|---|
&Vec<i32> | for i in list | &i32 | 需要*i解引用取值,不可修改元素 |
&Vec<i32> | for &i in list | i32 | 直接使用i,不可修改元素 |
&mut Vec<i32> | for i in &list | &i32 | 需要*i解引用取值,不可修改元素 |
&mut Vec<i32> | for &i in &list | i32 | 直接使用i,不可修改元素 |
&mut Vec<i32> | for i in &mut list | &mut i32 | 需要*i解引用取值,可修改元素 |
&mut Vec<i32> | for &mut i in &mut list | i32 | 直接使用i,可修改元素 |
测试用完整代码
fn main() { // 原练习需要修改列表,因此声明为可变 let mut list = vec![10, 14, 10, 12, 9, -2, 14, 10, 14]; let ret1 = loop1(&list); let ret2 = loop2(&list); // 取消注释会触发编译错误 // let ret3 = loop3_bad(&mut list); let ret4 = loop4(&mut list); let ret5 = loop5(&mut list); let ret6 = loop6(&mut list); let ret7 = loop7(&mut list); println!("loop1 ret={:?}", ret1); println!("loop2 ret={:?}", ret2); // println!("loop3 ret={:?}", ret3); println!("loop4 ret={:?}", ret4); println!("loop5 ret={:?}", ret5); println!("loop6 ret={:?}", ret6); println!("loop7 ret={:?}", ret7); } fn loop1(list: &Vec<i32>) -> Option<f64> { if list.is_empty() { None } else { let mut sum: f64 = 0.0; for &i in list { sum += f64::from(i); } Some(sum / list.len() as f64) } } fn loop2(list: &Vec<i32>) -> Option<f64> { if list.is_empty() { None } else { let mut sum: f64 = 0.0; for i in list { sum += f64::from(*i); } Some(sum / list.len() as f64) } } // 编译失败版本 // fn loop3_bad(list: &mut Vec<i32>) -> Option<f64> { // if list.is_empty() { // None // } else { // let mut sum: f64 = 0.0; // // 这里直接传list,会拿走可变引用的所有权 // for &mut i in list { // sum += f64::from(i); // } // // 此处list已经失效,调用len()会报错 // Some(sum / list.len() as f64) // } // } fn loop4(list: &mut Vec<i32>) -> Option<f64> { if list.is_empty() { None } else { let mut sum: f64 = 0.0; for i in &*list { sum += f64::from(*i); } Some(sum / list.len() as f64) } } fn loop5(list: &mut Vec<i32>) -> Option<f64> { if list.is_empty() { None } else { let mut sum: f64 = 0.0; for &i in &*list { sum += f64::from(i); } Some(sum / list.len() as f64) } } fn loop6(list: &mut Vec<i32>) -> Option<f64> { if list.is_empty() { None } else { let mut sum: f64 = 0.0; for &mut i in &mut *list { sum += f64::from(i); } Some(sum / list.len() as f64) } } fn loop7(list: &mut Vec<i32>) -> Option<f64> { if list.is_empty() { None } else { let mut sum: f64 = 0.0; for i in &mut *list { sum += f64::from(*i); } Some(sum / list.len() as f64) } }
内容的提问来源于stack exchange,提问作者dhu

