Rust 迭代器 Iterator & IntoIterator
迭代器允许我们迭代一个连续的集合,例如数组、动态数组 Vec、HashMap 等,在此过程中,只需关心集合中的元素如何处理,而无需关心如何开始、如何结束、按照什么样的索引去访问等问题。
For 循环与迭代器
迭代器与 for 循环最主要的差别就在于:是否通过索引来访问集合。严格来说,Rust 中的 for 循环是编译器提供的语法糖,最终还是对迭代器中的元素进行遍历。在 Rust 中,实现了 IntoIterator
trait 的类型都可以自动把类型集合转换为迭代器,然后通过 for 语法糖进行访问。
例如数组:
let arr = [1, 2, 3];
for v in arr {
peintln!("{}", v);
}
也是可以使用 IntoIterator
trait 的 into_iter
方法显式地将数组转换成迭代器。
let arr = [1, 2, 3];
for v in arr.into_iter() {
println!("{}", v);
}
注:数组不是迭代器(没有实现Iterator
),但是数组实现了IntoIterator
,Rust 通过 for 语法糖,自动把实现了该特征的数组类型转换为迭代器,最终让我们可以直接对一个数组进行迭代。
此外,还可以使用 for 循环对数值序列进行迭代,如
for i in 1..100 { ... };// 有限迭代器:对有限数值序列进行迭代
for i in (1..).into_iter() {...}; // 无限迭代器:无限长度的自增序列
Iterator trait 迭代器
Iterator
trait 定义:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 省略其余有默认实现的方法
}
在 Rust 中,迭代器是惰性的,也就是在定义迭代器后不使用将不会发生任何事,只有在使用时迭代器才会开始迭代其中的元素。
惰性初始化的方式确保了创建迭代器不会有任何额外的性能损耗,其中的元素也不会被消耗,只有使用到该迭代器的时候,一切才开始。
迭代器之所以成为迭代器,就是因为实现了 Iterator
trait,要实现该 trait,最主要的就是要实现其中的 next
方法。for 循环通过不断调用迭代器上的 next
方法来获取迭代器中的元素。
比如:
let arr = [1, 2, 3];
let mut arr_iter = arr.into_iter();
assert_eq!(arr_iter.next(), Some(1));
assert_eq!(arr_iter.next(), Some(2));
assert_eq!(arr_iter.next(), Some(3));
assert_eq!(arr_iter.next(), None);
迭代器本身也可以直接调用 next
方法,返回值是 Option 类型(有值时是Some(T),无值时是None),遍历是按照迭代器中元素的排列顺序依次进行的,同时手动迭代必须将迭代器声明为 mut
可变,因为调用 next
会改变迭代器其中的状态数据,使用 for 循环时则无需标注,因为for循环自动完成。
for
循环是迭代器的语法糖,大概原理如下:
let values = vec![1, 2, 3];
{
/// IntoIterator::into_iter完全限定的方式与into_iter方式values.into_iter()是等价的
let result = match IntoIterator::into_iter(values) {
/// 使用了loop循环配合next方法来遍历迭代器中的元素,当迭代器返回 None时,跳出循环
mut iter => loop {
match iter.next() {
Some(x) => { println!("{}", x); },
None => break,
}
},
};
result
}
总之,next
方法对迭代器的遍历是消耗性的,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 None
。
IntoIterator trait 转化成迭代器
IntoIterator
trait 定义:
trait IntoIterator
where
<Self::IntoIter as Iterator>::Item == Self::Item,
{
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
实现 IntoIterator
特性的类型可以被转换为迭代器。当用于 for-in
循环时,将自动调用该类型的 into_iter
方法。
比如动态 Vec 不仅实现了IntoIterator
trait,&Vec
与 &mut Vec
同样如此。因此我们可以相应的对可变与不可变的引用,以及自有值进行迭代。
Vec 实现了三种不同形式的 IntoInterator:
impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>
根据被遍历对象的类型,for 循环会选择使用哪个into_iter
方法并获得不同类型的元素:
- 可遍历对象的共享引用返回共享引用的元素 &T
- 可遍历对象的可变引用返回可变引用的元素 &mut T
- 拥有所有权的可遍历对象返回值 T。注意:这个可遍历对象被转移所有权,从而被会 drop 掉。
// vec = Vec<T>
for v in &vec {} // v = &T
// above example desugared
// 以上代码等价于
for v in (&vec).into_iter() {}
// vec = Vec<T>
for v in &mut vec {} // v = &mut T
// above example desugared
// 以上代码等价于
for v in (&mut vec).into_iter() {}
标准库的一揽子实现机制为迭代器Iterator
本身自动实现了 IntoIterator
trait:
impl<I: Iterator> IntoIterator for I {
type Item = I::Item;
type IntoIter = I;
#[inline]
fn into_iter(self) -> I {
self
}
}
所以 vec.into_iter()
与 vec.into_iter().into_iter().into_iter()
效果相同的。
fn main() {
let values = vec![1, 2, 3];
for v in values.into_iter().into_iter().into_iter() {
println!("{}",v)
}
}
into_iter,iter,iter_mut 产生迭代器的区别
into_iter()
返回 T,&T 或 &mut T 类型的 Iterator, 依赖于环境;iter()
迭代引用(&T):调用next
方法返回的类型是Some(&T)
;iter_mut()
迭代可变引用(&mut T):调用next
方法返回的类型是Some(&mut T)
;
注:Rust命名规律:into_
之类的,都是拿走所有权,_mut
之类的都是可变引用,剩下的就是不可变引用。
代码示例:
fn main() {
let values = vec![1, 2, 3];
// 所有权转移至迭代器中,values 将不能再使用
for v in values.into_iter() {
println!("{}", v)
}
// 下面的代码将报错,因为 values 的所有权在上面 `for` 循环中已经被转移走
// println!("{:?}",values);
let values = vec![1, 2, 3];
let _values_iter = values.iter();
// 不会报错,因为 values_iter 只是借用了 values 中的元素
println!("{:?}", values);
let mut values = vec![1, 2, 3];
// 对 values 中的元素进行可变借用
let mut values_iter_mut = values.iter_mut();
// 取出第一个元素,并修改为0
if let Some(v) = values_iter_mut.next() {
*v = 0;
}
// 输出[0, 2, 3]
println!("{:?}", values);
}
Iterator 和 IntoIterator 的区别
Iterator
是迭代器 trait,只有实现了它才能称为迭代器,才能调用 next
方法。
IntoIterator
强调的是某一个类型如果实现了该 trait,那么该类型数据可以通过 into_iter()
、iter()
或 iter_mut()
方法将其变成一个迭代器。
实现 Iterator<Item = T>
的类型可以迭代产生 T
类型。注意:并不存在 IteratorMut
类型,因为可以通过在实现 Iterator
特性时指定 Item
关联类型,来选择其返回的是不可变引用、可变引用还是自有值。
Vec<T> 方法 | 返回类型 |
---|---|
.iter() | Iterator<Item = &T> |
.iter_mut() | Iterator<Item = &mut T> |
.into_iter() | Iterator<Item = T> |
消费者与适配器
消费者是迭代器上的方法,它会消费掉迭代器中的元素,然后返回该类型的值,这些消费者都有一个共同的特点:在定义中都依赖 next
方法来消费元素。
消费性适配器:某些方法(比如collect
、 sum
等)在其内部会自动调用 next
方法,并会拿走迭代器的所有权,消耗掉迭代器上的元素。比如 sum
方法,它会拿走迭代器的所有权,并反复调用 next
来遍历迭代器并对里面的元素进行求和,并在迭代完成时返回总和。
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
// v1_iter 是借用了 v1,因此 v1 可以照常使用
println!("{:?}",v1);
// 以下代码会报错,因为 `sum` 拿到了迭代器 `v1_iter` 的所有权
// println!("{:?}",v1_iter);
}
迭代器适配器:Iterator
trait 中定义了另一类方法,允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1); // 警告,因为map方法返回一个迭代器,map 迭代器是惰性的,不产生任何效果
let v2: Vec<> = v1.iter().map(|x| x + 1).collect(); // 将v1中的数依次加1然后生成一个新的Vec,其中collect()方法就是一个消费者适配器
collect
方法是一个消费者适配器,可以将一个迭代器中的元素收集到指定类型中(注意:必须显式的告诉编译器想要收集成的集合类型),上面代码示例中的 Vec<_>
表明将迭代器中的元素收集成 Vec 类型,具体元素类型通过类型推导获得。
map
方法是一个迭代器适配器,会将迭代器中的每一个值进行一系列操作,然后把该值转换成另外一个新值,该操作通过闭包完成。
let names = ["tim", "tony"];
let ages = [11, 33];
let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();
zip
是一个迭代器适配器,其作用就是将两个迭代器的内容压缩到一起,形成 Iterator<Item=(ValueFromA, ValueFromB)>
这样的新的迭代器,然后通过 collect
方法将迭代器中的(K, V)形式的值收集成 HashMap<K, V>
。
之前的 map
方法中使用闭包作为迭代器适配器的参数,其最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值。下面的代码示例同时体现了这两个优点:
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect() //捕获外部环境中的变量 shoe_size 来与输入参数进行比较
}
filter
是迭代器适配器,用于对迭代器中的每个值进行过滤。它使用闭包作为参数,该闭包从迭代器中获取一项并返回一个 bool
,如果闭包返回 true
,其值将会包含在 filter
提供的新迭代器中。
为自定义类型实现 Iterator trait
前面使用了数组来创建迭代器,但其实基于其它集合类型一样可以创建迭代器,也可以创建自己的迭代器——只要为自定义类型实现 Iterator trait 即可。中文标准库中的 Module std::iter 中给出了如何实现迭代器的方法。
例如:
struct MyType {
items: Vec<String>
}
impl MyType {
fn iter(&self) -> impl Iterator<Item = &String> {
MyTypeIterator {
index: 0,
items: &self.items
}
}
}
struct MyTypeIterator<'a> {
index: usize,
items: &'a Vec<String>
}
impl<'a> Iterator for MyTypeIterator<'a> {
type Item = &'a String;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.items.len() {
None
} else {
let item = &self.items[self.index];
self.index += 1;
Some(item)
}
}
}
实现自己的迭代器非常简单,但是 Iterator
trait 中,只需要自己实现 next
一个方法,因为其他方法都有默认实现,并且这些默认实现的方法其实都是基于 next
方法实现的。
比如enumerate
是 Iterator
trait 上的方法,该方法产生一个新的迭代器,其每个元素都是元素(索引,值)。enumerate
是迭代器适配器,可以使用消费者迭代器或 for 循环对新产生的迭代器进行处理。
代码示例如下:
let v = vec![1u64, 2, 3, 4, 5, 6];
for (i, v) in v.iter().enumerate() {
println!("第{}个值是{}", i, v)
}
迭代器的性能
根据测试,使用迭代器和 for 循环完成同样的任务,迭代器的运行时间还要少一点。
迭代器是 Rust 的零成本抽象之一,这就意味着抽象并不会引入运行时开销。
总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性,可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开(Unrolling)、向量化、消除边界检查等优化手段,使得迭代器和 for
循环都有极为高效的执行效率。
总结
Rust 中实现了 Iterator
trait 才是迭代器,实现 IntoIterator
特性的类型可以被转换为迭代器,提供三个公共方法创建迭代器:iter()、iter_mut() 和 into_iter(),分别用于迭代 &T(引用)、&mut T(可变引用)和 T(值),其中,前两种是普通方法,而into_iter() 来自于 IntoIterator trait。
Rust 的 for 循环其实是迭代器语法糖,当没有显式的使用迭代器时,它会根据不同的上下文,分别使用 T、&T 和 &mut T 类型所实现的 into_iter() 返回的迭代器。
遍历类型返回不同遍历器的三个方法:
into_iter()
返回 T,&T 或 &mut T 类型的 Iterator,依赖于环境,IntoIterator
的方法;iter()
返回 Iterator<&T>,调用next
方法返回的类型是Some(&T)
;iter_mut()
返回 Iterator<&mut T>, 调用next
方法返回的类型是Some(&mut T)
;
参考
- course.rs/advance/fun…
- kaisery.github.io/trpl-zh-cn/…
- my_lv.gitbooks.io/rust/conten…
- rustwiki.org/zh-CN/rust-…
- rust.ffactory.org/std/iter/in…
- www.becomebetterprogrammer.com/rust-iter-v…
- github.com/pretzelhamm…
转载自:https://juejin.cn/post/7205508171523817532