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

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`

解决这个问题的通常方法是borrow 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`

为什么?为什么borrow 的范围不是迭代器,而是范围?是不是有不同的解读?

推荐答案

为了理解这里发生了什么,了解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都会从IntoIterator trait开始调用into_iter方法.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 ...

原因是,对于很多集合CIntoIterator特性不仅针对集合实现,还针对集合&C的共享引用实现,该实现生成共享项.(有时也为可变引用&mut C实现,它生成对项目的可变引用).

现在回到Range的例子,我们可以判断它是如何实现IntoIterator的.

奇怪的是,看着the reference docs for RangeRange似乎并没有直接实现IntoIterator...但如果我们查一下关于doc的Blanket Implementations部分.在rust-lang.org上,我们可以看到每个迭代器都实现了IntoIterator特性(很简单,只需返回自身):

impl<I> IntoIterator for I
where
    I: Iterator

这有什么帮助?那么,判断further up(在trait实现下),我们看到Range实现了Iterator:

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

因此Range通过Iterator的间接方式实现IntoIterator.然而,没有实现Iterator&Range<A>(这是不可能的)或IntoIterator&Range<A>.因此,我们可以通过按值传递Range来使用for循环,但不能通过引用.

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

从技术上讲,实现IntoIterator&Range是可能的,因为这可能会产生一个新的迭代器.但这与Range的整体迭代器实现发生冲突的可能性将非常高,而且情况将更加混乱.此外,Range最多是两个整数,复制这一点非常便宜,所以实现IntoIterator&Range真的没有什么大价值.

如果仍要使用collection,可以克隆它

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

这带来了另一个问题:如果我们可以克隆这个范围,而且复制它(如上所述)很便宜,那么这个范围为什么不实现Copy特性呢?然后.into_iter()调用将复制coll范围(而不是移动它),循环结束后仍可以使用它.According to this PR复制特征实现实际上存在,但被删除了,因为以下内容被认为是一个脚踏枪(为了指出这一点,请点击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();

最后,为了完整性,当我们在一个范围内循环时,看看实际调用了哪些方法可能是有益的:

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相关问答推荐

移植带有可变borrow 的C代码-卸载期间错误(nappgui示例)

如何从接收&;self的方法克隆RC

如何将元素添加到向量并返回对该元素的引用?

在UdpSocket上使用sendto时的隐式套接字绑定

如何在Rust中将选项<;选项<;字符串>;转换为选项<;选项&;str>;?

铁 rust 中双倍或更多换行符的更好练习?

获取与父字符串相关的&;str的原始片段

为什么&;mut buf[0..buf.len()]会触发一个可变/不可变的borrow 错误?

获取已知数量的输入

Rust 中什么时候可以返回函数生成的字符串切片&str?

信号量释放后 Rust 输出挂起线程

如何使用tracing-subscriberRust crate 构建多编写者、全局过滤订阅者

std mpsc 发送者通道在闭包中使用时关闭

更新 rust ndarray 中矩阵的一行

枚举的利基优化如何在 Rust 中工作?

Rust 打包在 .deb 中

是否可以在 Rust 中的特定字符上实现特征?

如何在 use_effect_with_deps 中设置监听器内的状态?

如何在 Rust 中创建最后一个元素是可变长度数组的 struct ?

当特征函数依赖于为 Self 实现的通用标记特征时实现通用包装器