`for...in` 循环中的 Rust 借用规则
Rust borrowing rules in `for...in` loop
为什么这三个 print_max
函数都有效?哪一个是最佳实践? for number in number_list
是 for number in number_list.iter()
的快捷方式吗?
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
print_max_1(&number_list);
print_max_2(&number_list);
print_max_3(&number_list);
}
fn print_max_1(number_list: &[u16]) {
let mut largest = &number_list[0]; // borrow the first number
for number in number_list.iter() { // borrowing?
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
fn print_max_2(number_list: &[u16]) {
let mut largest = &number_list[0]; // borrow the first number
for number in number_list { // shortcut for .iter()?
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
fn print_max_3(number_list: &[u16]) {
let mut largest = number_list[0]; // copying?
for &number in number_list.iter() { // borrowing?
if number > largest { // no error
largest = number;
}
}
println!("The largest number is {}", largest);
}
为什么这行不通?
fn print_max_4(number_list: &[u16]) {
let mut largest = &number_list[0];
for &number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
错误消息说 largest
是 &u16
而 number
是 u16
。为什么不是 number
&u16
?
让我们来解决这些问题 one-by-one。
print_max_1
这里,largest
是一个可变变量,它持有对 u16
的不可变引用(即它持有 &u16
)。在循环中,number
也是一个 &u16
,它与 largest
一样,是从 number_list
借来的。 number
和 larger
都被隐式取消引用以执行比较。如果 number
引用的值大于 larger
引用的值,则将 number
中包含的不可变引用的副本存储到 largest
.
中
print_max_2
这种情况下,由于number_list
本身是借用的,所以print_max_2
的解析与print_max_1
相同。
print_max_3
这里,largest
是一个u16
。复制number_list[0]
是对的,但值得注意的是这个复制便宜。在循环中,number_list
的每个元素都被复制并直接存储在number
中。如果 number
大于 largest
,则将新的最大值直接存储在 largest
中。这是您编写的三个实现中最优化的一个,因为您消除了引用(即指针)引入的所有不必要的间接访问。
print_max_4
您再次将对 number_list
的第一个元素的引用存储在 largest
中,即 largest
是 &u16
。类似地,与 print_max_3
中的情况一样,number
是一个 u16
,它将保存 number_list
中元素的副本。但是,正如您所指出的,这个函数是问题 child.
在循环中,编译器会指出两个错误:
- 您尝试比较两个没有定义
PartialOrder
的不同类型,即 largest
是 &u16
和 number
是 u16
。 Rust 不会试图通过这种比较来猜测您的意思,因此为了解决这个问题,您必须使 number
和 largest
成为同一类型。在这种情况下,您要做的是使用 *
运算符显式取消引用 largest
,这将允许您将它指向的 u16
与包含的 u16
进行比较number
。最后的比较看起来像
if number > *largest { ... }
- 您试图将
u16
存储在 &u16
类型的变量中,这没有意义。不幸的是,在这里您将 运行 撞到墙上。在循环中,您所拥有的只是从 number_list
复制的数字的值,但是 largest
需要将 reference 保存到 u16
.我们不能在这里简单地借用 number
(例如通过写 largest = &number
),因为 number
将在循环结束时被删除(即超出范围)。解决的唯一方法是通过存储最大 值 本身而不是指向它的指针来恢复到 print_max_2
。
至于for number in number_list
是否是for number in number_list.iter()
的捷径,答案是肯定的不是。前者将获得 number_list
的所有权,并且在每次迭代期间,number
将获得 number_list
中下一个值的所有权。相比之下,后者仅执行借用,并且在循环的每次迭代期间,number
接收到对 number_list
的下一个元素的不可变引用。
在这种特定情况下,这两个操作看起来是相同的,因为获取不可变引用的所有权只需要制作一个副本,从而使原始所有者保持完整。有关详细信息,请参阅 以了解有关 .into_iter()
和 .iter()
之间差异的相关问题。
这里有一些自动神奇的事情需要注意:
你的变量 'number_list' 是一个 std::vec::Vec
. You then use a slice for the function argument signatures. Vector has an implementation for the Deref
trait. In rust, this particular arrangement uses Deref coercion
to convert the vector with mentioned Deref trait into a std::slice
.
但是,向量和切片都可以使用 for 循环进行迭代。任何实现 std::iter::Iterator
特征的东西。
向量不这样做,而是实现 std::iter::IntoIterator
,正如它所说的那样,By implementing IntoIterator for a type, you define how it will be converted to an iterator. This is common for types which describe a collection of some kind.
查看 Implementing an Iterator 了解更多详情。特别是行:
all Iterators
implement IntoIterator
, by just returning themselves.
This means two things:
If you're writing an Iterator, you can use it with a for loop. If
you're creating a collection, implementing IntoIterator
for it will
allow your collection to be used with the for loop.
你会发现 Rust 提供了很多转换特征,以及一些自动隐藏的行为。随着 Rust 中类型的广泛使用,这有助于减少一些必要的转换。有关详细信息,请参阅 From and Into。
我讨厌 C++(隐藏代码),但在 Rust 中还不错。如果编译器允许你这样做,那么你可能已经找到了你需要的东西。有时,自动方式可能不够用,您可能需要使用支持 methods/functions 才能到达那里。
为什么这三个 print_max
函数都有效?哪一个是最佳实践? for number in number_list
是 for number in number_list.iter()
的快捷方式吗?
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
print_max_1(&number_list);
print_max_2(&number_list);
print_max_3(&number_list);
}
fn print_max_1(number_list: &[u16]) {
let mut largest = &number_list[0]; // borrow the first number
for number in number_list.iter() { // borrowing?
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
fn print_max_2(number_list: &[u16]) {
let mut largest = &number_list[0]; // borrow the first number
for number in number_list { // shortcut for .iter()?
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
fn print_max_3(number_list: &[u16]) {
let mut largest = number_list[0]; // copying?
for &number in number_list.iter() { // borrowing?
if number > largest { // no error
largest = number;
}
}
println!("The largest number is {}", largest);
}
为什么这行不通?
fn print_max_4(number_list: &[u16]) {
let mut largest = &number_list[0];
for &number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
错误消息说 largest
是 &u16
而 number
是 u16
。为什么不是 number
&u16
?
让我们来解决这些问题 one-by-one。
print_max_1
这里,largest
是一个可变变量,它持有对 u16
的不可变引用(即它持有 &u16
)。在循环中,number
也是一个 &u16
,它与 largest
一样,是从 number_list
借来的。 number
和 larger
都被隐式取消引用以执行比较。如果 number
引用的值大于 larger
引用的值,则将 number
中包含的不可变引用的副本存储到 largest
.
print_max_2
这种情况下,由于number_list
本身是借用的,所以print_max_2
的解析与print_max_1
相同。
print_max_3
这里,largest
是一个u16
。复制number_list[0]
是对的,但值得注意的是这个复制便宜。在循环中,number_list
的每个元素都被复制并直接存储在number
中。如果 number
大于 largest
,则将新的最大值直接存储在 largest
中。这是您编写的三个实现中最优化的一个,因为您消除了引用(即指针)引入的所有不必要的间接访问。
print_max_4
您再次将对 number_list
的第一个元素的引用存储在 largest
中,即 largest
是 &u16
。类似地,与 print_max_3
中的情况一样,number
是一个 u16
,它将保存 number_list
中元素的副本。但是,正如您所指出的,这个函数是问题 child.
在循环中,编译器会指出两个错误:
- 您尝试比较两个没有定义
PartialOrder
的不同类型,即largest
是&u16
和number
是u16
。 Rust 不会试图通过这种比较来猜测您的意思,因此为了解决这个问题,您必须使number
和largest
成为同一类型。在这种情况下,您要做的是使用*
运算符显式取消引用largest
,这将允许您将它指向的u16
与包含的u16
进行比较number
。最后的比较看起来像if number > *largest { ... }
- 您试图将
u16
存储在&u16
类型的变量中,这没有意义。不幸的是,在这里您将 运行 撞到墙上。在循环中,您所拥有的只是从number_list
复制的数字的值,但是largest
需要将 reference 保存到u16
.我们不能在这里简单地借用number
(例如通过写largest = &number
),因为number
将在循环结束时被删除(即超出范围)。解决的唯一方法是通过存储最大 值 本身而不是指向它的指针来恢复到print_max_2
。
至于for number in number_list
是否是for number in number_list.iter()
的捷径,答案是肯定的不是。前者将获得 number_list
的所有权,并且在每次迭代期间,number
将获得 number_list
中下一个值的所有权。相比之下,后者仅执行借用,并且在循环的每次迭代期间,number
接收到对 number_list
的下一个元素的不可变引用。
在这种特定情况下,这两个操作看起来是相同的,因为获取不可变引用的所有权只需要制作一个副本,从而使原始所有者保持完整。有关详细信息,请参阅 .into_iter()
和 .iter()
之间差异的相关问题。
这里有一些自动神奇的事情需要注意:
你的变量 'number_list' 是一个 std::vec::Vec
. You then use a slice for the function argument signatures. Vector has an implementation for the Deref
trait. In rust, this particular arrangement uses Deref coercion
to convert the vector with mentioned Deref trait into a std::slice
.
但是,向量和切片都可以使用 for 循环进行迭代。任何实现 std::iter::Iterator
特征的东西。
向量不这样做,而是实现 std::iter::IntoIterator
,正如它所说的那样,By implementing IntoIterator for a type, you define how it will be converted to an iterator. This is common for types which describe a collection of some kind.
查看 Implementing an Iterator 了解更多详情。特别是行:
all
Iterators
implementIntoIterator
, by just returning themselves. This means two things:If you're writing an Iterator, you can use it with a for loop. If you're creating a collection, implementing
IntoIterator
for it will allow your collection to be used with the for loop.
你会发现 Rust 提供了很多转换特征,以及一些自动隐藏的行为。随着 Rust 中类型的广泛使用,这有助于减少一些必要的转换。有关详细信息,请参阅 From and Into。
我讨厌 C++(隐藏代码),但在 Rust 中还不错。如果编译器允许你这样做,那么你可能已经找到了你需要的东西。有时,自动方式可能不够用,您可能需要使用支持 methods/functions 才能到达那里。