如何使用范围的一个示例是:
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`为什么会这样呢?为什么借来的范围不是迭代器,而是范围?它对它的解释是否不同?
发布于 2020-09-01 10:13:53
要了解这里正在发生的事情,了解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;
}因此,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的。
从范围的参考文档的角度来看,Range似乎没有直接实现IntoIterator .但是,如果我们查看doc.生锈-lang.org上的包层实现部分,我们可以看到每个迭代器都实现了IntoIterator特性(很小,只需要返回自己):
impl<I> IntoIterator for I
where
I: Iterator这有什么用?检查再往上走 (在特性实现下),我们看到Range确实实现了Iterator
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最多只有两个整数,而复制它非常便宜,因此实现&Range的IntoIterator实际上没有很大的价值。
如果您仍然希望使用集合,则可以克隆它。
for i in coll.clone() { /* ... */ }
// `coll` still available as the for loop used the clone这就引出了另一个问题:如果我们可以克隆范围,而且复制它(正如上面所说的)很便宜,那么为什么range不实现Copy特性呢?然后,.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,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() { /* ... */ }发布于 2020-09-01 10:13:45
范围是修改自己以生成元素的迭代器。因此,要遍历范围,就必须修改它(或者修改它的副本,如下所示)。
另一方面,向量本身并不是迭代器。当向量被循环时,调用.into_iter()来创建迭代器;向量本身不需要被消耗。
这里的解决方案是使用clone创建一个可以循环的新迭代器:
for i in coll.clone() {
println!("i is {}", i);
}(顺便说一句,println!系列的宏自动接受引用。)
发布于 2020-09-01 10:20:30
假设你有一个向量:
let v = vec![1, 2, 3];方法iter on Vec返回实现Iterator特性的内容。对于向量,还有一个特性Borrow (和BorrowMut)的实现,它不返回&Vec。相反,您将得到一个片&[T]。然后,可以使用这个切片来迭代向量的元素。
但是,范围(例如1..10)已经实现了IntoIterator,不需要将其转换为片或其他视图。因此,您可以通过调用into_iter() (这是隐式的)来使用范围本身。现在,就好像您将范围移到了某个函数中,并且不能再使用变量coll了。借用语法不会有帮助,因为这只是Vec的一些特殊功能。
在本例中,您可以从您的范围构建一个Vec (使用collect方法),在迭代它时克隆这个范围,或者在迭代之前获得它的长度(因为获取长度并不消耗范围本身)。
一些参考资料:
https://stackoverflow.com/questions/63685464
复制相似问题