iter和into_iter之间有什么区别?

460
我正在进行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返回的迭代器产生的是值,但对于一个array,这两个迭代器是相同的?
这两种方法的用例/ API是什么?
5个回答

457

TL;DR:

  • into_iter返回的迭代器可能会产生T&T&mut T,具体取决于上下文。
  • iter返回的迭代器按约定将产生&T
  • iter_mut返回的迭代器按约定将产生&mut T

第一个问题是:“什么是into_iter?”

into_iter来自IntoIterator trait:

pub trait IntoIterator 
where
    <Self::IntoIter as Iterator>::Item == Self::Item, 
{
    type Item;
    type IntoIter: Iterator;
    fn into_iter(self) -> Self::IntoIter;
}

当您想要指定特定类型如何转换为迭代器时,可以实现此特性。特别是,如果类型实现了IntoIterator,则可以在for循环中使用它。

例如,Vec实现了IntoIterator...三次!

impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>

每个变体都略有不同。

这个函数接受Vec和它的迭代器,产生值(直接使用T):

impl<T> IntoIterator for Vec<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    fn into_iter(mut self) -> IntoIter<T> { /* ... */ }
}
另外两个通过引用获取向量(不要被 into_iter(self) 的签名所迷惑,因为在这两种情况下,self 都是一个引用),它们的迭代器将产生对 Vec 中元素的引用。
这个产生不可变引用
impl<'a, T> IntoIterator for &'a Vec<T> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;

    fn into_iter(self) -> slice::Iter<'a, T> { /* ... */ }
}

而这个则会产生可变引用

impl<'a, T> IntoIterator for &'a mut Vec<T> {
    type Item = &'a mut T;
    type IntoIter = slice::IterMut<'a, T>;

    fn into_iter(self) -> slice::IterMut<'a, T> { /* ... */ }
}

所以:

iterinto_iter有什么区别?

into_iter是一个通用的方法,用于获取迭代器,无论这个迭代器产生值、不可变引用还是可变引用都取决于上下文,有时可能会令人惊讶。

iteriter_mut是特定情境下的方法。因此它们的返回类型与上下文无关,并且传统上分别为产生不可变引用和可变引用的迭代器。

Rust by Example文章的作者说明了依赖于调用into_iter的上下文(即类型)可能带来的惊喜,并且通过使用以下事实使问题更加复杂:

  1. IntoIterator未对[T; N]实现,仅对&[T; N]&mut [T; N]实现——它将在Rust 2021中实现。
  2. 当一个方法没有为一个值实现时,它会自动搜索对该值的引用

对于into_iter来说,这非常令人惊讶,因为除了[T; N]之外的所有类型都为所有3种变体(值和引用)实现了它。

数组以如此令人惊讶的方式实现了IntoIterator,以便可以在for循环中迭代对它们的引用。

从Rust 1.51开始,数组可以实现产生值的迭代器(通过array::IntoIter),但是自动引用的IntoIterator现有实现使通过IntoIterator进行按值迭代变得困难


38
我发现这篇博客很有帮助:http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html。 - poy
4
@DanM.: (1) 这意味着 into_iter 根据接收者是值、引用还是可变引用来选择实现。 (2) Rust 中不存在可变值,或者说,由于所有权关系,任何值都是可变的。 - Matthieu M.
2
@Ixx:谢谢,这非常有用。我决定在问题的顶部提供一个 TL;DR,以避免将答案埋在中间,你觉得怎么样? - Matthieu M.
1
有关实现IntoIter的数组,请参见https://github.com/rust-lang/rust/issues/65798。 - Lucas Werkmeister
1
值得注意的是,在 Rust 2021 版本中,数组将实现 IntoIter,并且计划将其回溯到所有版本,以便它在所有版本中基本上都能正常工作:https://blog.rust-lang.org/2021/05/11/edition-2021.html - Korny
显示剩余13条评论

334

我从谷歌搜索来这里,寻找一个其他答案没有提供的简单答案。这就是那个简单的答案:

  • iter() 通过引用迭代项目
  • iter_mut() 迭代项目,为每个项目提供一个可变引用
  • into_iter() 迭代项目,将它们移动到新的范围内

所以for x in my_vec { ... }本质上等同于 my_vec.into_iter().for_each(|x| ... ) - 两者都将my_vec的元素移动到...作用域中。

如果你只需要查看数据,请使用iter,如果你需要编辑/更改它,请使用iter_mut,如果你需要给它一个新的主人,请使用into_iter

这很有帮助: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html


6
这是针对my_vec或者你调用.into_iter()的东西实际上是Vec<T>的情况下的简单好答案。其他答案更加复杂,因为它们还提到,如果你在其他东西上调用.into_iter(),比如&Vec<T>或者&[](或者等效地,在for ... in ...循环中迭代它们),你可能实际上没有将项目移动到新的作用域中。(至少,这是我目前的理解...) - betaveros
15
是的,"into_iter()会遍历这些项,并将它们移入到新的作用域中"并不总是准确的 - 请参考被接受的回答以获取更多细节。 - Michael Dorst

21
我认为还有一些需要澄清的地方。例如,集合类型(如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_iteriter的区别

回到关于into_iteriter之间的区别的问题。正如其他人指出的那样,区别在于into_iterIntoIterator的一个必需方法,它可以产生任何在IntoIterator::Item中指定的类型。通常,如果一个类型实现了IntoIterator<Item=I>,按照惯例它也有两个特定的方法:iteriter_mut,分别产生&I&mut I

这意味着我们可以创建一个函数,该函数接收具有into_iter方法的类型(即可迭代类型)并使用特征绑定:

fn process_iterable<I: IntoIterator>(iterable: I) {
    for item in iterable {
        // ...
    }
}

然而,我们不能使用特质绑定来要求类型具有iter方法或iter_mut方法,因为它们只是约定俗成的。我们可以说into_iteriteriter_mut更加通用。

iteriter_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];

// Below is equivalent to: `for item in v.iter() {`
for item in &v {
    println!("{}", item);
}

如果v.iter()等同于&v,因为两者都实现了IntoIterator<Item=&T>,那么Rust为什么还提供两者呢?这是为了符合人体工程学。在for循环中,使用&vv.iter()更加简洁;但在其他情况下,v.iter()(&v).into_iter()更加清晰:
let v = vec![1, 2];

let a: Vec<i32> = v.iter().map(|x| x * x).collect();
// Although above and below are equivalent, above is a lot clearer than below.
let b: Vec<i32> = (&v).into_iter().map(|x| x * x).collect();

同样,在for循环中,v.iter_mut()可以替换为&mut v

let mut v = vec![1, 2];

// Below is equivalent to: `for item in v.iter_mut() {`
for item in &mut v {
    *item *= 2;
}

何时为类型提供into_iteriter方法

如果该类型只有一种迭代方式,我们应该同时实现两种方法。但是,如果它有两种或更多的迭代方式,我们应该为每种方式提供一个临时方法。

例如,String既不提供into_iter也不提供iter,因为它有两种迭代方式:按字节迭代其表示形式或按字符迭代其表示形式。相反,它提供了两个方法:bytes用于迭代字节,chars用于迭代字符,作为iter方法的替代品。


* 技术上讲,我们可以通过创建一个特质来实现它。但是这样我们需要为每种要使用的类型实现这个特质。同时,std 中的许多类型已经实现了 IntoIterator


9

.into_iter()方法只适用于&[],而不适用于数组本身。例如:

impl<'a, T> IntoIterator for &'a [T]
    type Item = &'a T

使用

impl<T> IntoIterator for Vec<T>
    type Item = T

由于IntoIterator仅在&[T]上定义,因此当您使用值时,无法像Vec一样丢弃切片本身。 (值无法移出)

现在,为什么会这样是一个不同的问题,我也想自己学习。 推测:数组是数据本身,切片只是对其的视图。 在实践中,您无法将数组作为值移入另一个函数,只能传递其视图,因此您也无法在那里使用它。


IntoIterator 也被实现于 &'a mut [T],因此它 可能 会将对象从数组中移出。我认为这与返回结构体 IntoIter<T> 没有生命周期参数有关,而 Iter<'a, T> 则有,因此前者无法持有切片。 - rodrigo
mut 的意思是你可以改变值,而不是将它们移出。 - viraptor
1
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x }); => "错误:无法移动借用内容" - viraptor
相关:在底层,“移动”是什么意思?我原本以为它意味着它实际上“移动”数据(例如从堆栈到堆)。此外,“移动”和“按值获取”是同一件事吗? - vitiral
1
逐个解释:是的,into_iter 返回 &T,因为您实际上是在调用 &array.into_iter()。这并不是真正的魔法——对所有方法来说都是自动发生的(需要和将被自动添加)。无法移动数组只是一个次要的相关评论;它可能会或可能不会解释答案。移动值意味着它在之前的位置不再有效,而是出现在新位置。例如,对于 vec2 就会发生这种情况。在此代码结束时,您可以访问 vec1[0],但不能访问 vec2[0] - viraptor
显示剩余3条评论

2

IntoIteratorIterator 通常被使用如下。

我们为那些有一个内部/嵌套值(或者在一个引用后)并且实现了 Iterator 或者拥有中间的 "Iter" 结构的结构体实现 IntoIterator

例如,让我们创建一个 "新" 的数据结构:

struct List<T>; 
// Looks something like this:
// - List<T>(Option<Box<ListCell<T>>>)
// - ListCell<T> { value: T, next: List<T> }

我们希望这个 List<T> 可以迭代,所以这应该是实现 Iterator 的好地方,对吗?是的,我们可以这样做,但这会在某些方面限制我们。
相反,我们创建一个中间的“可迭代”结构,并实现 Iterator 特性:
// NOTE: I have removed all lifetimes to make it less messy.
struct ListIter<T> { cursor: &List<T> };
impl<T> Iterator for ListIter<T> {
  type Item = &T;
  fn next(&mut self) -> Option<Self::Item> {...}
}

现在我们需要以某种方式连接List<T>ListIter<T>。可以通过为List实现IntoIterator来完成此操作。

impl<T> IntoIterator for List<T> {
    type Item = T;
    type Iter = ListIter<Self::Item>; 
    fn into_iter(self) -> Self::Iter { ListIter { cursor: &self } }
}
IntoIterator可以为容器结构实现多次,例如,如果它包含不同的嵌套可迭代字段或者我们有一些更高级的类型情况。假设我们有一个Collection<T>: IntoIterator特征,将由多个数据结构实现,例如List<T>Vector<T>Tree<T>,它们也有各自的Iter:ListIter<T>VectorIter<T>TreeIter<T>。

但是,当我们从通用代码转换为具体代码时,这实际上意味着什么?
fn wrapper<C>(value: C) where C: Collection<i32> {
  let iter = value.into_iter() // But what iterator are we?
  ...
}

这段代码并不完全正确,生命周期和可变性支持被省略了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接