`for...in` 循环中的 Rust 借用规则

Rust borrowing rules in `for...in` loop

为什么这三个 print_max 函数都有效?哪一个是最佳实践? for number in number_listfor 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&u16numberu16。为什么不是 number &u16

让我们来解决这些问题 one-by-one。

print_max_1

这里,largest 是一个可变变量,它持有对 u16 的不可变引用(即它持有 &u16)。在循环中,number 也是一个 &u16,它与 largest 一样,是从 number_list 借来的。 numberlarger 都被隐式取消引用以执行比较。如果 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.

在循环中,编译器会指出两个错误:

  1. 您尝试比较两个没有定义 PartialOrder 的不同类型,即 largest&u16numberu16。 Rust 不会试图通过这种比较来猜测您的意思,因此为了解决这个问题,您必须使 numberlargest 成为同一类型。在这种情况下,您要做的是使用 * 运算符显式取消引用 largest,这将允许您将它指向的 u16 与包含的 u16 进行比较number。最后的比较看起来像
    if number > *largest { ... }
    
  2. 您试图将 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 才能到达那里。