微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决为什么借用的范围不是迭代器,而是范围?

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

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`

那是为什么?为什么借用的范围不是迭代器,而是范围?是对它的不同解释吗?

解决方法

要了解这里发生的情况,了解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从into_iter特征开始调用IntoIterator方法。 IntoIterator特征看起来(大约)是这样的:

trait IntoIterator {
    // ...
    type IntoIter;
    fn into_iter(self) -> Self::IntoIter;
}

因此into_iterself作为值,并返回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.rust-lang.org的Blanket Implementations部分, ,我们可以看到每个迭代器都实现了IntoIterator特质(很简单,只需返回自身即可):

impl<I> IntoIterator for I
where
    I: Iterator

这有什么帮助?好吧,检查further up(在特征实现下),我们看到Range确实实现了Iterator

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

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

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

从技术上来说,可以为&mut Range实现Iterator,因为这可能会产生一个新的迭代器。但是,这可能会与IntoIterator的覆盖式迭代器实现发生冲突的可能性非常高,并且情况将更加令人困惑。此外,&Range最多是两个整数,并且将其复制非常便宜,因此为Range实现Range确实没有太大的价值。

如果您仍然想使用收藏,可以对其进行克隆

IntoIterator

这带来了另一个问题:如果我们可以克隆范围并且复制它很便宜(如上所述),为什么Range不实现&Range特性?然后,for i in coll.clone() { /* ... */ } // `coll` still available as the for loop used the clone 调用将复制范围Copy(而不是移动它),并且在循环后仍可以使用它。 According to this PR实际上存在复制特征实现,但是由于以下内容被视为步枪而被删除了(Michael Anderson指出了这一点)

.into_iter()

还请注意,coll确实实现了迭代器,因此您可以这样做

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,9]` as might have been expected
let v: Vec<_> = iter.collect();

最后,为了完整起见,在遍历Range时查看实际调用了哪些方法可能是有益的:

&mut Range

被翻译为

let mut iter = 1..10;
for i in &mut iter {
    if i > 2 { break; }
}
// `[4,9]` as expected
let v: Vec<_> = iter.collect();

我们可以使用限定的方法语法来明确这一点:

for item in 1..10 { /* ... */ }
,

范围是用于修改自身以生成元素的迭代器。因此,要遍历某个范围,有必要对其进行修改(或其副本,如下所示)。

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

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

for i in coll.clone() { 
    println!("i is {}",i);
}

(偶然地,println!系列宏会自动获取引用。)

,

假设您有一个向量:

let v = vec![1,3];

iter上的方法Vec返回实现Iterator特征的某物。使用向量,还可以实现特征Borrow(和BorrowMut)的实现,尽管它不返回&Vec。相反,您得到了一个切片&[T]。然后,可以使用该切片来迭代矢量的元素。

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

在这种情况下,您可以从范围中构造一个Vec(使用collect方法),在对其进行迭代时克隆该范围,或者在迭代之前获取长度(因为获取长度不会会消耗范围本身)。

一些参考文献:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。