简体   繁体   English

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

[英]Rust borrowing rules in `for...in` loop

Why would all these three print_max functions work?为什么这三个print_max函数都能工作? Which one is the best practice?哪一个是最佳实践? Is for number in number_list a shortcut for for number in number_list.iter() ? for number in number_listfor number in number_list.iter() for number in number_list的快捷方式吗?

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);
}

Why wouldn't this work?为什么这行不通?

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);
} 

The error message says that largest is &u16 while number is u16 .错误消息说largest&u16numberu16 Why isn't number &u16 ?为什么不是number &u16

Let's tackle these one-by-one.让我们一一解决这些问题。

print_max_1

Here, largest is a mutable variable that holds an immutable reference to a u16 (ie it holds a &u16 ).这里, largest是一个可变变量,它持有对u16的不可变引用(即它持有&u16 )。 Within the loop, number is also a &u16 which, like largest , is borrowed from number_list .在循环中, number&u16其中,像largest ,从借来的number_list Both number and larger are implicitly dereferenced to perform the comparison. numberlarger都被隐式取消引用以执行比较。 If the value referenced by number is greater than that referenced by larger , you store a copy of the immutable reference contained in number into largest .如果引用的值number比通过引用更大larger ,你存储包含处于不可改变引用的副本numberlargest

print_max_2

In this case, since number_list is itself borrowed, the analysis of print_max_2 is identical to print_max_1 .在这种情况下,由于number_list本身借用的分析print_max_2相同print_max_1

print_max_3

Here, largest is a u16 .这里, largest是一个u16 You are correct that number_list[0] is copied, but it is worth noting that this copy is cheap .复制number_list[0]是正确的,但值得注意的是,此副本很便宜 Within the loop, each element of number_list is copied and stored directly in number .在循环中, number_list每个元素number_list被复制并直接存储在number If number is greater than largest , you stored the new greatest value directly in largest .如果number大于largest ,则将新的最大值直接存储在largest This is the most optimal of the three implementations you've written, since you do away with all of the unnecessary indirection that references (ie, pointers) introduce.这是您编写的三个实现中最优化的,因为您消除了引用(即指针)引入的所有不必要的间接性。

print_max_4

Once again, you store a reference to the first element of number_list in largest , ie largest is a &u16 .再一次,您将number_list的第一个元素的引用存储在largest ,即largest是一个&u16 Similarly, as was the case in print_max_3 , number is a u16 , which will hold copies of the elements from number_list .类似地,与print_max_3中的情况一样, numberu16 ,它将保存number_list元素的number_list However, as you noted, this function is the problem child.但是,正如您所指出的,此功能是问题子项。

Within the loop, the compiler will point out two errors:在循环中,编译器会指出两个错误:

  1. You attempt to compare two distinct types which don't have a PartialOrder defined, namely largest which is a &u16 and number which is a u16 .您尝试比较两种没有定义PartialOrder不同类型,即largest&u16numberu16 Rust isn't in the business of trying to guess what you mean by this comparison, so in order fix this, you'll have to make both number and largest the same type. Rust 不会试图猜测这种比较的含义,因此为了解决这个问题,您必须将numberlargest设为相同的类型。 In this case, what you want to do is explicitly dereference largest using the * operator, which will allow you to compare the u16 it points to with the u16 contained in number .在这种情况下,你想要做什么明确的就是废弃largest使用*运算符,它可以让你比较u16它指向与u16包含在number This final comparison looks like最后的比较看起来像
    if number > *largest { ... }
  2. You attempt to store a u16 in a variable of type &u16 , which does not make sense.您尝试将u16存储在&u16类型的变量中,这是没有意义的。 Unfortunately, here you're going to run into a wall.不幸的是,在这里你会碰到一堵墙。 Within the loop, all you have is the value of the number you copied from number_list , but largest needs to hold a reference to a u16 .在循环中,您所拥有的只是您从number_list复制的数字的值,但largest需要保存对u16引用 We can't simply borrow number here (eg by writing largest = &number ), since number will be dropped (ie go out of scope) at the end of the loop.我们不能在这里简单地借用number (例如,通过编写largest = &number ),因为number将在循环结束时被删除(即超出范围)。 The only way to resolve is is to revert by to print_max_2 by storing the maximum value itself instead of the pointer to it.要解决是唯一的办法是,以恢复print_max_2通过存储最大值本身,而不是指向它的指针。

As for whether for number in number_list is a shortcut for for number in number_list.iter() , the answer is a firm no .至于 for for number in number_list是否是for number in number_list.iter()的快捷方式,答案是否定的 The former will take ownership of number_list , and during each iteration, number takes ownership of the next value in number_list .前者将拥有number_list所有权,并且在每次迭代期间, number将拥有number_list下一个值的number_list In contrasts, the latter only performs a borrow, and during each iteration of the loop, number receives an immutable reference to the next element of number_list .相比之下,后者只执行借位,并且在循环的每次迭代期间, number都会收到对number_list下一个元素的不可变引用。

In this specific case, these two operation appear identical, since taking ownership of an immutable reference simply entails making a copy, which leaves the original owner intact.在这种特定情况下,这两个操作看起来是相同的,因为获得不可变引用的所有权只需要制作一个副本,从而使原始所有者保持不变。 For more information, see this answer to a related question on the difference between .into_iter() and .iter() .有关更多信息,请参阅有关.into_iter().iter()之间区别的相关问题的答案

There are a few things happening auto-magically here to note:这里有一些事情会自动神奇地发生:

Your variable 'number_list' is a std::vec::Vec .您的变量 'number_list' 是std::vec::Vec You then use a slice for the function argument signatures.然后将切片用于函数参数签名。 Vector has an implementation for the Deref trait. Vector 有一个Deref trait 的实现。 In rust, this particular arrangement uses Deref coercion to convert the vector with mentioned Deref trait into a std::slice .Deref coercion ,这种特殊的安排使用Deref coercion转换将具有提到的 Deref 特征的向量转换为std::slice

However, both vectors and slices can be iterated by using a for loop.但是,向量和切片都可以使用 for 循环进行迭代。 Anything that implements the std::iter::Iterator trait.任何实现std::iter::Iterator特性的东西。 The vector doesn't do this, but rather implements std::iter::IntoIterator , which as it puts it, 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. vector 不这样做,而是实现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. 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. Take a look at Implementing an Iterator for more details.有关更多详细信息,请查看实现迭代器 Specifically the lines:具体线路:

all Iterators implement IntoIterator , by just returning themselves.所有Iterators IntoIterator通过返回自身来实现IntoIterator This means two things:这意味着两件事:

If you're writing an Iterator, you can use it with a for loop.如果您正在编写迭代器,则可以将它与 for 循环一起使用。 If you're creating a collection, implementing IntoIterator for it will allow your collection to be used with the for loop.如果您正在创建一个集合,为它实现IntoIterator将允许您的集合与 for 循环一起使用。

You'll find that Rust provides a lot of conversion traits, and some automatic hidden behavior.你会发现 Rust 提供了很多转换特性,以及一些自动隐藏的行为。 With the extensive type use in Rust, this helps alleviate some of the necessary conversions.由于 Rust 中广泛使用类型,这有助于减轻一些必要的转换。 See From and Into for more details there.有关更多详细信息,请参阅From 和 Into

I hated this about C++ (hidden code), however it's not so bad in Rust.我讨厌 C++(隐藏代码),但是在 Rust 中它并没有那么糟糕。 If the compiler lets you do it, then you've probably found what you need.如果编译器允许您这样做,那么您可能已经找到了您需要的东西。 Sometimes, the automatic way may not be adequate, and you may need to use supporting methods/functions to get there.有时,自动方式可能不够用,您可能需要使用支持方法/功能来实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM