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

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_iterIntoIteratortrait调用该方法。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)

原因是对于很多集合CIntoIterator特性不仅为集合实现,还为集合的共享引用实现,&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. 但是,没有实现Iteratorfor &Range<A>(这是不可能的)或IntoIteratorfor &Range<A>。因此,我们可以通过Range按值传递而不是通过引用来使用 for 循环。

为什么&Range不能执行Iterator?迭代器需要跟踪“它在哪里”,这需要某种变异,但我们不能变异 a,&Range因为我们只有一个共享引用。所以这是行不通的。(请注意,&mut Range可以并且确实实现了Iterator- 稍后会详细介绍)。

技术上可以实现IntoIterator&Range因为这可以产生一个新的迭代器。但是这与 的全面迭代器实现发生冲突的可能性Range非常高,事情会更加混乱。此外, aRange最多是两个整数,复制它非常便宜,因此实现IntoIteratorfor确实没有太大价值&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)

  • 当当,这是一个很好的完整答案。我正在考虑在其上实现的特征,并且与共享引用不同。最后一行清楚地解释了原因,例如某些集合可以做到这一点,而“Range”则不能。 (2认同)
  • 我想这里唯一缺少的部分是“Range”没有实现“Copy”。如果确实如此,那么在“into_iter”调用之后仍然可用。这里给出了不实现 Copy 的原因 https://github.com/rust-lang/rust/pull/27186 (2认同)

L. *_* F. 13

范围是修改自身以生成元素的迭代器。因此,要循环一个范围,就需要修改它(或它的一个副本,如下所示)。

另一方面,向量本身不是迭代器。 .into_iter()当向量循环时调用以创建迭代器;不需要消耗向量本身。

这里的解决方案是使用clone来创建一个可以循环的新迭代器:

for i in coll.clone() { 
    println!("i is {}", i);
}
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,println!宏系列会自动获取引用。)