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

2024-04-14

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

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`

这是为什么?为什么借用的范围不是迭代器,但范围却是?它的解释不同吗?


要了解这里发生的情况,了解 Rust 中的 for 循环如何工作会很有帮助。

基本上,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;
}

So into_iter takes self按价值和回报Self::IntoIter这是迭代器的类型。当 Rust 移动任何按值获取的参数时,事情.into_iter()调用后(或 for 循环后)不再可用。这就是为什么你不能使用coll在你的第一个代码片段中。

到目前为止一切都很好,但是如果我们像下面这样循环引用集合,为什么我们仍然可以使用集合呢?

for i in &collection {
    // ...
}
// can still use collection here ...

原因是对于很多收藏C, the IntoIterator不仅为集合实现了特征,还为集合的共享引用实现了特征&C并且此实现会产生共享项目。 (有时它也被实现为可变引用&mut C它产生对项目的可变引用)。

现在回到这个例子Range我们可以检查它是如何实现的IntoIterator.

看着Range 的参考文档 https://doc.rust-lang.org/std/ops/struct.Range.html, Range奇怪的是似乎没有实施IntoIterator直接...但是如果我们检查一揽子实施 https://doc.rust-lang.org/std/ops/struct.Range.html#blanket-implementationsdoc.rust-lang.org 上的部分,我们可以看到每个迭代器都实现了IntoIterator特征(简单地,只需返回自身):

impl<I> IntoIterator for I
where
    I: Iterator

这有什么帮助?嗯,检查一下进一步向上 https://doc.rust-lang.org/std/ops/struct.Range.html#impl-Iterator(在特征实现下)我们看到Range确实实施Iterator:

impl<A> Iterator for Range<A>
where
    A: Step, 

因此Range确实实施IntoIterator通过间接Iterator。然而,两者都没有实施Iterator for &Range<A>(这是不可能的)或IntoIterator for &Range<A>。因此,我们可以通过传递以下内容来使用 for 循环Range按值,但不按引用。

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

技术上可以实现IntoIterator for &Range因为这可以产生一个新的迭代器。但这有可能与一揽子迭代器的实现发生冲突Range会非常高,事情会更加混乱。此外,一个Range最多是两个整数,复制它非常便宜,所以实现起来确实没有太大价值IntoIterator for &Range.

如果你还想使用集合,可以克隆它

for i in coll.clone() { /* ... */ }
// `coll` still available as the for loop used the clone

这就提出了另一个问题:如果我们可以克隆范围并且复制它(如上所述)很便宜,为什么 Range 不实现Copy特征?然后.into_iter()调用将复制范围coll(而不是移动它)并且它在循环之后仍然可以使用。根据这个公关 https://github.com/rust-lang/rust/pull/21846#issuecomment-110526401Copy 特征实现实际上存在,但被删除,因为以下内容被认为是脚枪(帽子提示迈克尔·安德森 https://stackoverflow.com/users/221955/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() { /* ... */ }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么借用的范围不是迭代器,但范围却是? 的相关文章

随机推荐