首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么借来的范围不是迭代器,而是范围?

为什么借来的范围不是迭代器,而是范围?
EN

Stack Overflow用户
提问于 2020-09-01 09:59:01
回答 3查看 7.2K关注 0票数 60

如何使用范围的一个示例是:

代码语言:javascript
复制
let coll = 1..10;
for i in coll {
    println!("i is {}", &i);
}
println!("coll length is {}", coll.len());

这会失败的

代码语言:javascript
复制
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,但在这里不起作用:

代码语言:javascript
复制
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`

为什么会这样呢?为什么借来的范围不是迭代器,而是范围?它对它的解释是否不同?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-09-01 10:13:53

要了解这里正在发生的事情,了解for循环是如何在锈蚀中工作的是很有帮助的。

基本上,for循环是使用迭代器的短手,因此:

代码语言:javascript
复制
for item in some_value {
    // ...
}

基本上是一个短手

代码语言:javascript
复制
let mut iterator = some_value.into_iter();
while let Some(item) = iterator.next() {
    // ... body of for loop here
}

因此我们可以看到,无论我们用for循环进行循环,Rust都会从into_iter特性开始调用IntoIterator方法。IntoIterator特性看起来(大致)如下:

代码语言:javascript
复制
trait IntoIterator {
    // ...
    type IntoIter;
    fn into_iter(self) -> Self::IntoIter;
}

因此,into_iter按值获取self并返回Self::IntoIter,这是迭代器的类型。当Rust移动由值获取的任何参数时,调用.into_iter()的东西在调用后(或在for循环之后)就不再可用了。这就是为什么不能在第一个代码段中使用coll的原因。

到目前为止,还不错,但是为什么我们仍然可以使用一个集合,如果我们循环它的引用,如下所示?

代码语言:javascript
复制
for i in &collection {
    // ...
}
// can still use collection here ...

原因是,对于许多集合CIntoIterator特性不仅是为集合实现的,而且也是为了对集合&C的共享引用实现的,并且这个实现会生成共享项。(有时,它也是为可变引用实现的,&mut C生成对项的可变引用)。

现在回到使用Range的示例,我们可以检查它是如何实现IntoIterator的。

范围的参考文档的角度来看,Range似乎没有直接实现IntoIterator .但是,如果我们查看doc.生锈-lang.org上的包层实现部分,我们可以看到每个迭代器都实现了IntoIterator特性(很小,只需要返回自己):

代码语言:javascript
复制
impl<I> IntoIterator for I
where
    I: Iterator

这有什么用?检查再往上走 (在特性实现下),我们看到Range确实实现了Iterator

代码语言:javascript
复制
impl<A> Iterator for Range<A>
where
    A: Step, 

因此,Range确实通过Iterator的间接实现了IntoIterator。但是,无论是Iterator for &Range<A> (这是不可能的)还是IntoIterator for &Range<A>都没有实现。因此,我们可以通过通过值传递Range,而不是通过引用来使用for循环。

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

从技术上讲,为IntoIterator实现&Range是可能的,因为这可能会产生一个新的迭代器。但是,这与Range的通用迭代器实现发生冲突的可能性很高,甚至更令人困惑。此外,一个Range最多只有两个整数,而复制它非常便宜,因此实现&RangeIntoIterator实际上没有很大的价值。

如果您仍然希望使用集合,则可以克隆它。

代码语言:javascript
复制
for i in coll.clone() { /* ... */ }
// `coll` still available as the for loop used the clone

这就引出了另一个问题:如果我们可以克隆范围,而且复制它(正如上面所说的)很便宜,那么为什么range不实现Copy特性呢?然后,.into_iter()调用将复制范围coll (而不是移动它),它仍然可以在循环之后使用。根据这个公关 --复制特性实现实际上已经存在,但是被删除了,因为以下内容被认为是一种步兵( 迈克尔·安德森指出这一点的顶帽提示):

代码语言:javascript
复制
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确实实现了迭代器,所以您可以这样做

代码语言:javascript
复制
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();

最后,为了完整起见,当我们遍历一个范围时,看看实际调用了哪些方法可能是有指导意义的:

代码语言:javascript
复制
for item in 1..10 { /* ... */ }

被翻译成

代码语言:javascript
复制
let mut iter = 1..10.into_iter();
//                   ˆˆˆˆˆˆˆˆˆ--- which into_iter() is this?
while let Some(item) = iter.next() { /* ... */ }

我们可以使用限定的方法语法将其显式化:

代码语言:javascript
复制
let mut iter = std::iter::Iterator::into_iter(1..10);
// it's `Iterator`s  method!  ------^^^^^^^^^
while let Some(item) = iter.next() { /* ... */ }
票数 71
EN

Stack Overflow用户

发布于 2020-09-01 10:13:45

范围是修改自己以生成元素的迭代器。因此,要遍历范围,就必须修改它(或者修改它的副本,如下所示)。

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

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

代码语言:javascript
复制
for i in coll.clone() { 
    println!("i is {}", i);
}

(顺便说一句,println!系列的宏自动接受引用。)

票数 14
EN

Stack Overflow用户

发布于 2020-09-01 10:20:30

假设你有一个向量:

代码语言:javascript
复制
let v = vec![1, 2, 3];

方法iter on Vec返回实现Iterator特性的内容。对于向量,还有一个特性Borrow (和BorrowMut)的实现,它不返回&Vec。相反,您将得到一个片&[T]。然后,可以使用这个切片来迭代向量的元素。

但是,范围(例如1..10)已经实现了IntoIterator,不需要将其转换为片或其他视图。因此,您可以通过调用into_iter() (这是隐式的)来使用范围本身。现在,就好像您将范围移到了某个函数中,并且不能再使用变量coll了。借用语法不会有帮助,因为这只是Vec的一些特殊功能。

在本例中,您可以从您的范围构建一个Vec (使用collect方法),在迭代它时克隆这个范围,或者在迭代之前获得它的长度(因为获取长度并不消耗范围本身)。

一些参考资料:

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63685464

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档