我正在做Rust by Example教程,其中包含以下代码片段:
// Vec example
let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];
// `iter()` for vecs yields `&i32`. Destructure to `i32`.
println!("2 in vec1: {}", vec1.iter() .any(|&x| x == 2));
// `into_iter()` for vecs yields `i32`. No destructuring required.
println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));
// Array example
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
// `iter()` for arrays yields `&i32`.
println!("2 in array1: {}", array1.iter() .any(|&x| x == 2));
// `into_iter()` for arrays unusually yields `&i32`.
println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2));
我完全糊涂了--对于Vec
,从iter
返回的迭代器产生引用,从into_iter
返回的迭代器产生值,但对于数组,这些迭代器是相同的吗?
这两种方法的用例/API是什么?
5条答案
按热度按时间bvpmtnay1#
TL;DR:
into_iter
返回的迭代器可能产生T
、&T
或&mut T
中的任何一个,具体取决于上下文。iter
返回的迭代器将生成&T
。iter_mut
返回的迭代器将生成&mut T
。第一个问题是:“什么是
into_iter
?”into_iter
来源于IntoIterator
trait:当你想指定一个特定的类型如何被转换成迭代器时,你可以实现这个特性。最值得注意的是,如果一个类型实现了
IntoIterator
,它可以在for
循环中使用。例如,
Vec
实现了IntoIterator
...三次!每个变体都略有不同。
这个函数使用
Vec
,它的迭代器产生值(直接使用T
):另外两个通过引用获取向量(不要被
into_iter(self)
的签名所迷惑,因为self
在这两种情况下都是一个引用),它们的迭代器将产生对Vec
内部元素的引用。这个函数产生不可变的引用:
而这个函数产生可变引用:
因此:
iter
和into_iter
之间有什么区别?into_iter
是一个通用方法,用于获取迭代器,无论该迭代器生成值、不可变引用还是可变引用都取决于上下文,有时会令人惊讶。iter
和iter_mut
是ad-hoc方法。因此,它们的返回类型独立于上下文,并且通常分别是产生不可变引用和可变引用的迭代器。Rust by Example帖子的作者说明了对调用
into_iter
的上下文(即类型)的依赖所带来的惊喜,并且还使用以下事实来使问题复杂化:IntoIterator
没有为[T; N]
实现,只为&[T; N]
和&mut [T; N]
实现--它将用于Rust 2021。1.如果没有为某个值实现方法,则会自动搜索该值的 * 引用 *
这对于X1 M30 N1 X是非常令人惊讶的,因为所有类型(除了X1 M31 N1 X)都对所有3种变体(值和引用)实现了它。
数组实现了
IntoIterator
(以一种令人惊讶的方式),使得在for
循环中迭代对它们的引用成为可能。从Rust 1.51开始,数组可以实现一个生成值的迭代器(通过
array::IntoIter
),但是IntoIterator
的现有实现会自动引用makes it hard to implement by-value iteration viaIntoIterator
。p5cysglq2#
我从谷歌来这里寻求一个简单的答案,这是其他答案所没有的。这里是这个简单的答案:
iter()
按引用迭代项iter_mut()
迭代这些项,并为每个项提供一个可变引用into_iter()
迭代这些项,将它们移动到新的作用域中所以
for x in my_vec { ... }
本质上等价于my_vec.into_iter().for_each(|x| ... )
--两者都把move
的元素my_vec
纳入到...
的范围。如果您只需要 * 查看 * 数据,请使用
iter
;如果您需要编辑/修改数据,请使用iter_mut
;如果您需要给予数据新的所有者,请使用into_iter
。这很有帮助:http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html
dba5bblo3#
我认为还有一些事情需要进一步澄清。集合类型,比如
Vec<T>
和VecDeque<T>
,有into_iter
方法,该方法生成T
,因为它们实现了IntoIterator<Item=T>
。没有什么可以阻止我们创建一个类型Foo<T>
,如果对它进行迭代,它将不会生成T
,而是生成另一个类型U
。也就是说,Foo<T>
实作IntoIterator<Item=U>
。实际上,在
std
中有一些例子:&Path
实现了IntoIterator<Item=&OsStr>
,而&UnixListener
实现了IntoIterator<Item=Result<UnixStream>>
。into_iter
和iter
之间的差异回到
into_iter
和iter
之间的区别。与其他人所指出的类似,区别在于into_iter
是IntoIterator
的必需方法,它可以生成IntoIterator::Item
中指定的任何类型。通常,如果一个类型实现了IntoIterator<Item=I>
,按照约定,它也有两个特别的方法:iter
和iter_mut
,其分别产生&I
和&mut I
。这意味着我们可以创建一个函数,通过使用一个特征绑定来接收一个具有
into_iter
方法的类型(即它是一个可迭代的):然而,我们不能 * 使用一个trait绑定来要求一个类型拥有
iter
方法或iter_mut
方法,因为它们只是约定。我们可以说into_iter
比iter
或iter_mut
更广泛地使用。iter
和iter_mut
的替代方案另一个值得注意的事情是,
iter
并不是获得生成&T
的迭代器的唯一方法。(再次),std
中具有iter
方法的集合类型SomeCollection<T>
也具有其不可变引用类型&SomeCollection<T>
实现IntoIterator<Item=&T>
。例如,&Vec<T>
实现IntoIterator<Item=&T>
,所以它使我们能够迭代&Vec<T>
:如果
v.iter()
等价于&v
,因为两者都实现了IntoIterator<Item=&T>
,那么为什么Rust同时提供了这两个呢?这是出于人体工程学的考虑。在for
循环中,使用&v
比使用v.iter()
更简洁;但在其他情况下,v.iter()
比(&v).into_iter()
清楚得多:类似地,在
for
循环中,v.iter_mut()
可以替换为&mut v
:何时为类型提供(实现)
into_iter
和iter
方法如果类型只有一种“方式”可以迭代,我们应该实现两种方式。但是,如果有两种或更多的方式可以迭代,我们应该为每种方式提供一个特别的方法。
例如,
String
既不提供into_iter
,也不提供iter
,因为有两种方法可以对其进行迭代:迭代其字节表示形式或迭代其字符表示形式。相反,它提供了两种方法:bytes
用于迭代字节,chars
用于迭代字符,作为iter
方法的替代方法。impl
这个trait,同时,std
中的许多类型已经实现了IntoIterator
。lnvxswe24#
.into_iter()
不是为数组本身实现的,而是仅为&[]
实现的。比较:与
由于
IntoIterator
仅在&[T]
上定义,因此在使用值时,不能像删除Vec
那样删除切片本身。(不能移出值)现在,为什么会这样是一个不同的问题,我想了解自己。推测:数组是数据本身,切片只是它的一个视图。实际上,你不能把数组作为一个值移动到另一个函数中,只是传递它的一个视图,所以你也不能在那里使用它。
gr8qqesn5#
IntoIterator
和Iterator
通常是这样使用的。我们实现
IntoIterator
的结构有一个内部/嵌套的值(或在一个引用后面),它要么实现Iterator
,要么有一个中间的“Iter”结构。例如,让我们创建一个“新”数据结构:
我们希望这个
List<T>
是可迭代的,所以这里应该是实现Iterator
的好地方,对吗?是的,我们可以这样做,但是这会在某些方面限制我们。相反,我们创建一个中间的“可迭代”结构并实现
Iterator
特征:所以现在我们需要以某种方式连接
List<T>
和ListIter<T>
,这可以通过为List实现IntoIterator
来完成。IntoIterator
也可以为容器结构实现多次,例如,如果它包含不同的嵌套可迭代字段,或者我们有一些更高级的类型情况。假设我们有一个
Collection<T>: IntoIterator
特性,它将由多个数据结构实现,例如List<T>
、Vector<T>
和Tree<T>
,它们也有各自的Iter;ListIter<T>
、VectorIter<T>
和TreeIter<T>
。但是,当我们从通用代码转到特定代码时,这实际上意味着什么呢?此代码不是100%正确,忽略了生存期和可变性支持。