范围如何被消耗的一个例子是:
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/