dei*_*tch 59 iterator reference rust
范围如何被消耗的一个例子是:
let coll = 1..10;
for i in coll {
println!("i is {}", &i);
}
println!("coll length is {}", coll.len());
Run Code Online (Sandbox Code Playgroud)
这将失败
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`
Run Code Online (Sandbox Code Playgroud)
解决这个问题的通常方法是借用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`
Run Code Online (Sandbox Code Playgroud)
这是为什么?为什么借用的范围不是迭代器,但范围是?是不是有不同的解释?
Pau*_*aul 58
要了解这里发生的事情,了解 for 循环在 Rust 中的工作原理会很有帮助。
基本上,for 循环是使用迭代器的简写,因此:
for item in some_value {
// ...
}
Run Code Online (Sandbox Code Playgroud)
基本上是一个简写
let mut iterator = some_value.into_iter();
while let Some(item) = iterator.next() {
// ... body of for loop here
}
Run Code Online (Sandbox Code Playgroud)
所以我们可以看到,无论我们用 for 循环进行什么循环,Rust 都会into_iter
从IntoIterator
trait调用该方法。IntoIterator 特性看起来(大约)是这样的:
trait IntoIterator {
// ...
type IntoIter;
fn into_iter(self) -> Self::IntoIter;
}
Run Code Online (Sandbox Code Playgroud)
因此,into_iter
需要self
通过价值和回报Self::IntoIter
这是迭代器的类型。当 Rust 移动任何按值获取的参数时,.into_iter()
被调用的事物在调用之后(或在 for 循环之后)不再可用。这就是您不能coll
在第一个代码片段中使用的原因。
到目前为止一切顺利,但是如果我们像下面这样循环引用它,为什么我们仍然可以使用它呢?
trait IntoIterator {
// ...
type IntoIter;
fn into_iter(self) -> Self::IntoIter;
}
Run Code Online (Sandbox Code Playgroud)
原因是对于很多集合C
,IntoIterator
特性不仅为集合实现,还为集合的共享引用实现,&C
并且这个实现产生共享项。(有时它也为可变引用实现,&mut C
它产生对项目的可变引用)。
现在回到示例中,Range
我们可以检查它是如何实现的IntoIterator
。
纵观参考文档的范围,Range
奇怪的是似乎没有实现IntoIterator
直接......但如果我们检查毯实现上doc.rust-lang.org部分,我们可以看到,每一个迭代器实现了IntoIterator
性状(平凡,由刚刚返回本身):
impl<I> IntoIterator for I
where
I: Iterator
Run Code Online (Sandbox Code Playgroud)
这有什么帮助?好吧,进一步检查(在 trait 实现下)我们看到它Range
确实实现了Iterator
:
for i in &collection {
// ...
}
// can still use collection here ...
Run Code Online (Sandbox Code Playgroud)
因此Range
确实IntoIterator
通过Iterator
. 但是,没有实现Iterator
for &Range<A>
(这是不可能的)或IntoIterator
for &Range<A>
。因此,我们可以通过Range
按值传递而不是通过引用来使用 for 循环。
为什么&Range
不能执行Iterator
?迭代器需要跟踪“它在哪里”,这需要某种变异,但我们不能变异 a,&Range
因为我们只有一个共享引用。所以这是行不通的。(请注意,&mut Range
可以并且确实实现了Iterator
- 稍后会详细介绍)。
技术上可以实现IntoIterator
,&Range
因为这可以产生一个新的迭代器。但是这与 的全面迭代器实现发生冲突的可能性Range
非常高,事情会更加混乱。此外, aRange
最多是两个整数,复制它非常便宜,因此实现IntoIterator
for确实没有太大价值&Range
。
如果你还想使用集合,你可以克隆它
impl<I> IntoIterator for I
where
I: Iterator
Run Code Online (Sandbox Code Playgroud)
这带来了另一个问题:如果我们可以克隆范围并且(如上所述)复制它很便宜,那么为什么 Range 不实现该Copy
特征?然后.into_iter()
调用将复制范围coll
(而不是移动它)并且它仍然可以在循环之后使用。根据这个 PR, Copy trait 实现实际上存在但被删除了,因为以下内容被认为是一把枪(感谢Michael Anderson指出这一点):
impl<A> Iterator for Range<A>
where
A: Step,
Run Code Online (Sandbox Code Playgroud)
另请注意,&mut Range
确实实现了迭代器,因此您可以这样做
for i in coll.clone() { /* ... */ }
// `coll` still available as the for loop used the clone
Run Code Online (Sandbox Code Playgroud)
最后,为了完整起见,当我们遍历 Range 时,查看实际调用了哪些方法可能会有所帮助:
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();
Run Code Online (Sandbox Code Playgroud)
被翻译成
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();
Run Code Online (Sandbox Code Playgroud)
我们可以使用限定的方法语法使其明确:
for item in 1..10 { /* ... */ }
Run Code Online (Sandbox Code Playgroud)
L. *_* F. 13
范围是修改自身以生成元素的迭代器。因此,要循环一个范围,就需要修改它(或它的一个副本,如下所示)。
另一方面,向量本身不是迭代器。 .into_iter()
当向量循环时调用以创建迭代器;不需要消耗向量本身。
这里的解决方案是使用clone
来创建一个可以循环的新迭代器:
for i in coll.clone() {
println!("i is {}", i);
}
Run Code Online (Sandbox Code Playgroud)
(顺便说一句,println!
宏系列会自动获取引用。)