我认为还有一些需要澄清的地方。例如,集合类型(如
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
方法的类型(即可迭代类型)并使用特征绑定:
fn process_iterable<I: IntoIterator>(iterable: I) {
for item in iterable {
}
}
然而,我们不能使用特质绑定来要求类型具有iter
方法或iter_mut
方法,因为它们只是约定俗成的。我们可以说into_iter
比iter
或iter_mut
更加通用。
iter
和iter_mut
的替代方法
另一个有趣的观察是,iter
不是获得产生&T
迭代器的唯一方法。按照约定(再次),在std
中具有iter
方法的集合类型SomeCollection<T>
也使其不可变引用类型&SomeCollection<T>
实现了IntoIterator<Item=&T>
。例如,&Vec<T>
implements IntoIterator<Item=&T>
,因此它使我们能够迭代&Vec<T>
:
let v = vec![1, 2];
for item in &v {
println!("{}", item);
}
如果
v.iter()
等同于
&v
,因为两者都实现了
IntoIterator<Item=&T>
,那么Rust为什么还提供两者呢?这是为了符合人体工程学。在
for
循环中,使用
&v
比
v.iter()
更加简洁;但在其他情况下,
v.iter()
比
(&v).into_iter()
更加清晰:
let v = vec![1, 2];
let a: Vec<i32> = v.iter().map(|x| x * x).collect();
let b: Vec<i32> = (&v).into_iter().map(|x| x * x).collect();
同样,在for
循环中,v.iter_mut()
可以替换为&mut v
:
let mut v = vec![1, 2];
for item in &mut v {
*item *= 2;
}
何时为类型提供into_iter
和iter
方法
如果该类型只有一种迭代方式,我们应该同时实现两种方法。但是,如果它有两种或更多的迭代方式,我们应该为每种方式提供一个临时方法。
例如,String
既不提供into_iter
也不提供iter
,因为它有两种迭代方式:按字节迭代其表示形式或按字符迭代其表示形式。相反,它提供了两个方法:bytes
用于迭代字节,chars
用于迭代字符,作为iter
方法的替代品。
* 技术上讲,我们可以通过创建一个特质来实现它。但是这样我们需要为每种要使用的类型实现这个特质。同时,std
中的许多类型已经实现了 IntoIterator
。
into_iter
根据接收者是值、引用还是可变引用来选择实现。 (2) Rust 中不存在可变值,或者说,由于所有权关系,任何值都是可变的。 - Matthieu M.IntoIter
的数组,请参见https://github.com/rust-lang/rust/issues/65798。 - Lucas WerkmeisterIntoIter
,并且计划将其回溯到所有版本,以便它在所有版本中基本上都能正常工作:https://blog.rust-lang.org/2021/05/11/edition-2021.html - Korny