我正在通过阅读“这本书”来自学 Rust。当我通过此链接进行第一个练习时:https://doc.rust-lang.org/book/ch08-03-hash-maps.html ,我发现使用 for 循环获取正确的类型确实很棘手。我最终通过反复试验使代码可以工作,但我对它的工作原理感到非常困惑。
在这里,我简化了我遇到的一些问题,并将我的问题留在了内文中。我尝试了尽可能多的变化,但我认为主要的困惑是:
loop1
和loop2
之间的区别(我相信 3 和 4、5 和 6 是类似的)loop1
和loop2
中的内联问题loop3_bad
有什么问题?
fn main() {
// using mut because it is needed in the original code
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);
// cannot write f64::from(*i)
// error would be:
// error[E0614]: type `i32` cannot be dereferenced
//
// How should I read the for syntax?
// Is it because "&i" of &i32 type, therefore i is of "i32" type?
// or should I treat "&i" a declaration that i is of "&i32" type?
}
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);
// cannot write f64::from(i)
// error would be:
// the trait `From<&i32>` is not implemented for `f64`
//
// is i of "&i32" type?
// is the dereferencing required here?
}
Some(sum/list.len() as f64)
}
}
// This one causes compilation error, but why?
// If `list` is moved by the loop, why didn't this cause issue in loop1()?
//
// Rust ERROR:
//
// error[E0382]: borrow of moved value: `list`
// --> for_loop_example.rs:65:18
// |
// 57 | fn loop3(list: &mut Vec<i32>) -> Option<f64> {
// | ---- move occurs because `list` has type `&mut Vec<i32>`, which does not implement the `Copy` trait
// ...
// 62 | for &mut i in list {
// | ----
// | |
// | `list` moved due to this implicit call to `.into_iter()`
// | help: consider borrowing to avoid moving into the for loop: `&list`
// ...
// 65 | Some(sum/list.len() as f64)
// | ^^^^ value borrowed here after move
// |
// note: this function takes ownership of the receiver `self`, which moves `list`
//
// fn loop3_bad(list: &mut Vec<i32>) -> Option<f64> {
// if list.is_empty() {
// None
// } else {
// let mut sum: f64 = 0.0;
// for &mut i in list {
// sum += f64::from(i);
// }
// 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 {
// what does &*list even do?
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 { // similar to loop4, excpet for using &i
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 { // looking similar to loop5
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 { // looking similar to loop4, except for using mut
sum += f64::from(*i);
}
Some(sum/list.len() as f64)
}
}
最佳答案
这里几乎没有构建 block 。
当您迭代 &Vec
时,您将获得对其内容的共享引用。也就是说,迭代 &Vec<i32>
会给你 &i32
类型的元素。当您迭代&mut Vec
时,你会得到可变引用 - &mut i32
- 允许您更改内容,而不仅仅是检查内容(此外,当您迭代 Vec
时,您将拥有 i32
,但这与我们的讨论无关)。
要知道的第二件事是,在 Rust 中,实际上有两种方法来取消引用引用:第一种是通常的 *
,第二是使用模式。当我绑定(bind)一个新变量时,
let x = value;
x
其实不只是一个名字,而是一个pattern (一个irrefutable一个)。我用过identifier pattern这允许我将值绑定(bind)到名称,但还有其他类型。其中之一是the reference pattern :&
或&mut
然后是嵌套模式。它的作用是取消引用该值,然后将嵌套模式绑定(bind)到取消引用的值。因此,如下:
let &i = &value;
相当于let i = value;
(您还可以看到对称性,这就是选择此语法的原因)。
不仅仅是let
允许模式:Rust 中的任何绑定(bind)都允许。参数,match
参数,以及 for
循环。
有了这些知识,让我们一一理解循环:
在 loop1
,我们正在迭代 &Vec<i32>
。这给了我们 &i32
s。但是,我们正在使用 &i
是模式 - 这意味着 i
仅绑定(bind)到i32
部分。由于这不是引用,因此您不能再取消引用它。
loop2
是相同的,但没有引用模式。这里i
是 &i32
,因此您必须取消引用它。
loop3_bad
很有趣。它的作用与loop1
相同。但具有可变引用。但这不起作用。共享引用文献是Copy
,这样您就可以访问list
即使在它被循环移动之后 - 但可变引用则不然,因此编译器会犯“借用移动值”的错误。
其余的循环正在执行所谓的“重新借用”,已经解释了共享/可变引用以及使用/不使用引用模式的各种组合。重新借用只是&*
或&mut *
,这就是解除引用然后立即获取引用。听起来不太有趣,而且它确实对共享引用没有影响,但对于可变引用它有重要的语义:你不能复制可变引用,但你可以重新借用它。 &mut *v
使您能够获得可能较短的生命周期,而不会影响原始引用。一旦不再需要重新借用的引用,您可以将其丢弃并再次使用原始引用。
由于我们只重借,list
不为所动。通常编译器会自动执行重新借用 - 请参阅 Do mutable references have move semantics? - 但与 for
循环,但它没有。这就是为什么loop3
没有重新借用就失败了,但是 loop5
成功
顺便说一句,不要拿&Vec<i32>
作为参数,使用 &[i32]
相反:Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?
关于for-loop - Rust 中的 for 循环变体之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72808249/