如何解决为什么借用的范围不是迭代器,而是范围?
如何消耗范围的一个示例是:
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`
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_iter
取self
作为值,并返回Self::IntoIter
,它是迭代器的类型。当Rust移动任何由值接受的参数时,调用.into_iter()
之后的调用在调用之后(或for循环之后)将不再可用。这就是为什么您不能在第一个代码段中使用coll
的原因。
到目前为止还不错,但是如果我们像下面这样循环引用它,为什么还可以使用一个集合呢?
for i in &collection {
// ...
}
// can still use collection here ...
原因是,对于许多集合C
,IntoIterator
特质不仅为集合实现,而且还为集合&C
和此实现提供了共享引用产生共享项。 (有时也为可变引用&mut C
实现,该可变引用生成对项的可变引用。)
现在回到带有Range
的示例,我们可以检查其如何实现IntoIterator
。
看看the reference docs for Range,Range
似乎并不直接实现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 举报,一经查实,本站将立刻删除。