rust - 为什么借用的范围不是迭代器,但范围是?

标签 rust reference iterator

范围如何被消耗的一个例子是:

let coll = 1..10;
for i in coll {
    println!("i is {}", &i);
}
println!("coll length is {}", coll.len());
这将失败
error[E0382]: borrow of moved value: `coll`
   --> src/main.rs:6:35
    |
2   |     let coll = 1..10;
    |         ---- move occurs because `coll` has type `std::ops::Range<i32>`, which does not implement the `Copy` trait
3   |     for i in coll {
    |              ----
    |              |
    |              `coll` moved due to this implicit call to `.into_iter()`
    |              help: consider borrowing to avoid moving into the for loop: `&coll`
...
6   |     println!("coll length is {}", coll.len());
    |                                   ^^^^ value borrowed here after move
    |
note: this function consumes the receiver `self` by taking ownership of it, which moves `coll`
解决此问题的常用方法是借用 coll ,但这在这里不起作用:
error[E0277]: `&std::ops::Range<{integer}>` is not an iterator
 --> src/main.rs:3:14
  |
3 |     for i in &coll {
  |              -^^^^
  |              |
  |              `&std::ops::Range<{integer}>` is not an iterator
  |              help: consider removing the leading `&`-reference
  |
  = help: the trait `std::iter::Iterator` is not implemented for `&std::ops::Range<{integer}>`
  = note: required by `std::iter::IntoIterator::into_iter`
这是为什么?为什么借用的范围不是迭代器,但范围是?是不是有不同的解释?

最佳答案

要了解这里发生的事情,了解 for 循环在 Rust 中的工作原理会很有帮助。
基本上,for 循环是使用迭代器的简写,因此:

for item in some_value {
    // ...
}
基本上是一个简写
let mut iterator = some_value.into_iter();
while let Some(item) = iterator.next() {
    // ... body of for loop here
}
所以我们可以看到,无论我们用 for 循环循环什么,Rust 都会调用 into_iter方法来自 IntoIterator性状。 IntoIterator 特性看起来(大约)是这样的:
trait IntoIterator {
    // ...
    type IntoIter;
    fn into_iter(self) -> Self::IntoIter;
}
所以into_iter需要 self按值返回 Self::IntoIter这是迭代器的类型。随着 Rust 移动任何按值获取的参数,事物 .into_iter()在调用之后(或在 for 循环之后)被调用不再可用。这就是为什么你不能使用 coll在你的第一个代码片段中。
到目前为止一切顺利,但是如果我们像下面这样循环引用它,为什么我们仍然可以使用它呢?
for i in &collection {
    // ...
}
// can still use collection here ...
原因是对于很多收藏C , IntoIterator trait 不仅为集合实现,还为集合的共享引用实现 &C这个实现产生共享项目。 (有时它也为可变引用实现 &mut C,它产生对项目的可变引用)。
现在回到 Range 的例子我们可以检查它是如何实现的 IntoIterator .
看着 the reference docs for Range , Range奇怪的是似乎没有实现 IntoIterator直接...但如果我们检查 Blanket Implementations在 doc.rust-lang.org 部分,我们可以看到每个迭代器都实现了 IntoIterator trait(简单来说,只是返回自身):
impl<I> IntoIterator for I
where
    I: Iterator
这有什么帮助?嗯,检查 further up (在 trait 实现下)我们看到 Range确实实现 Iterator :
impl<A> Iterator for Range<A>
where
    A: Step, 
因此 Range确实实现 IntoIterator通过 Iterator 的间接.但是,没有实现 Iterator&Range<A> (这是不可能的)或 IntoIterator&Range<A> .因此,我们可以通过传递 Range 来使用 for 循环。按值,而不是按引用。
为什么可以&Range未实现 Iterator ?迭代器需要跟踪“它在哪里”,这需要某种变异,但我们不能变异 &Range因为我们只有一个共享引用。所以这是行不通的。 (请注意 &mut Range 可以并且确实实现了 Iterator - 稍后会详细介绍)。
在技​​术上可以实现 IntoIterator&Range因为这可能会产生一个新的迭代器。但这可能会与 Range 的全面迭代器实现发生冲突。会非常高,事情会更加困惑。此外,还有一个 Range最多是两个整数,复制这个很便宜,所以实现 IntoIterator 真的没有什么大的值(value)。为 &Range .
如果你还想使用集合,你可以克隆它
for i in coll.clone() { /* ... */ }
// `coll` still available as the for loop used the clone
这带来了另一个问题:如果我们可以克隆范围并且复制它(如上所述)便宜,那么为什么 Range 不实现 Copy特征?然后是.into_iter()调用将复制范围 coll (而不是移动它)并且它仍然可以在循环后使用。 According to this PR Copy trait 实现实际上存在,但被删除了,因为以下内容被认为是一个footgun(提示 Michael Anderson 指出这一点):
let mut iter = 1..10;
for i in iter {
    if i > 2 { break; }
}
// This doesn't work now, but if `Range` implemented copy,
// it would produce `[1,2,3,4,5,6,7,8,9]` instead of 
// `[4,5,6,7,8,9]` as might have been expected
let v: Vec<_> = iter.collect();
另请注意 &mut Range确实实现了迭代器,所以你可以做
let mut iter = 1..10;
for i in &mut iter {
    if i > 2 { break; }
}
// `[4,5,6,7,8,9]` as expected
let v: Vec<_> = iter.collect();
最后,为了完整起见,当我们遍历 Range 时,查看实际调用了哪些方法可能会有所帮助:
for item in 1..10 { /* ... */ }
被翻译成
let mut iter = 1..10.into_iter();
//                   ˆˆˆˆˆˆˆˆˆ--- which into_iter() is this?
while let Some(item) = iter.next() { /* ... */ }
我们可以使用限定的方法语法使其明确:
let mut iter = std::iter::Iterator::into_iter(1..10);
// it's `Iterator`s  method!  ------^^^^^^^^^
while let Some(item) = iter.next() { /* ... */ }

关于rust - 为什么借用的范围不是迭代器,但范围是?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63685464/

相关文章:

rust - Rust 宏中的意外标记

c++ - 如何确定 2 个路径是否在可移植 C++ 中引用同一个文件

c++ - 将 C++ 数组指针的过去末尾转换为迭代器是否合法?

c++ - 遍历字符串 vector ,从控制台获取输入,给出段错误

java - 如何检查在迭代器中强制转换为 T 是否安全?

rust - 是否可以使用 Pin 在堆栈而不是堆上创建自引用结构?

rust - 为什么使用异步 block 然后使我的流取消固定?

arrays - Perl:在 Foreach 循环中分配对数组元素的引用

rust - 实现二叉搜索树时不能多次借用节点作为可变节点

java - Java中不同引用类型的使用